Merge branch 'next' into typescript-config

# Conflicts:
#	docs/configure/overview.md
This commit is contained in:
Norbert de Langen 2022-07-06 20:15:16 +02:00
commit a5eace1db0
No known key found for this signature in database
GPG Key ID: FD0E78AF9A837762
573 changed files with 11154 additions and 3719 deletions

View File

@ -277,9 +277,9 @@ jobs:
- run:
name: run e2e tests cra
command: yarn test:e2e-framework --pnp cra
- run:
name: run e2e tests vue
command: yarn test:e2e-framework --pnp sfcVue
# - run:
# name: run e2e tests vue
# command: yarn test:e2e-framework --pnp sfcVue
- run:
name: prep artifacts
when: always

View File

@ -14,6 +14,25 @@
},
{
"pattern": "https://stackblitz.com/*"
},
{
"pattern": "https://*.chromatic.com"
},
{
"pattern": "https://www.chromatic.com/build?*"
},
{
"pattern": "http://*.nodeca.com"
},
{
"pattern": "http://definitelytyped.org/*"
},
{
"pattern": "https://yoursite.com/*"
},
{
"pattern": "https://my-specific-domain.com"
}
]
],
"aliveStatusCodes": [429, 200]
}

View File

@ -1,3 +1,123 @@
## 6.5.0-rc.1 (May 18, 2022)
### Bug Fixes
- CLI: Improve webpack version and add detection of nextjs ([#18220](https://github.com/storybookjs/storybook/pull/18220))
- ArgsTable: Gracefully handle conditional args failures ([#18248](https://github.com/storybookjs/storybook/pull/18248))
- Controls: Fix reset button broken for !undefined URL values ([#18231](https://github.com/storybookjs/storybook/pull/18231))
- Vue3: Add support for TSX in single file components ([#18038](https://github.com/storybookjs/storybook/pull/18038))
## 6.5.0-rc.0 (May 17, 2022)
### Features
- Addon-a11y: Show % of users in toolbar menu ([#18003](https://github.com/storybookjs/storybook/pull/18003))
### Bug Fixes
- Web-components: Clean Lit Expression comments in story source ([#18108](https://github.com/storybookjs/storybook/pull/18108))
- Vue: Map args correctly in CSF3 implicit render function ([#18209](https://github.com/storybookjs/storybook/pull/18209))
- Vue3: Fix CSF3 implicit render function when storyStoreV7 is enabled ([#18208](https://github.com/storybookjs/storybook/pull/182)
### Maintenance
- CLI: Don't throw is Ctrl + C was pressed when selecting a package in the build command ([#18195](https://github.com/storybookjs/storybook/pull/18195))
- Build: Cleanup noise from unit tests ([#18196](https://github.com/storybookjs/storybook/pull/18196))
### Dependency Upgrades
- Fixed PnP compatibility for bundled components package ([#18015](https://github.com/storybookjs/storybook/pull/18015))
## 6.5.0-beta.8 (May 11, 2022)
### Bug Fixes
- Composition: Fix metadata.json incorrectly overriding main.js refs versions ([#18185](https://github.com/storybookjs/storybook/pull/18185))
### Maintenance
- Examples: Set channelOptions to disallow function serialization ([#18071](https://github.com/storybookjs/storybook/pull/18071))
### Dependency Upgrades
- Upgrade to telejson 6 ([#18164](https://github.com/storybookjs/storybook/pull/18164))
## 6.5.0-beta.7 (May 9, 2022)
### Features
- CSF3: Add title prefix support for stories with custom titles ([#17724](https://github.com/storybookjs/storybook/pull/17724))
### Bug Fixes
- Components: Fix race conditions in SyntaxHighlighter ([#18158](https://github.com/storybookjs/storybook/pull/18158))
### Maintenance
- API: Deprecate isToolshown, rename to showToolbar ([#18131](https://github.com/storybookjs/storybook/pull/18131))
## 6.5.0-beta.6 (May 6, 2022)
### Bug Fixes
- Controls: Fix undefined args handling ([#18135](https://github.com/storybookjs/storybook/pull/18135))
### Maintenance
- CLI: Update Introduction.stories.mdx template to be MDX2-friendly ([#18141](https://github.com/storybookjs/storybook/pull/18141))
### Dependency Upgrades
- Remove jest from cli peerDependencies ([#18149](https://github.com/storybookjs/storybook/pull/18149))
## 6.5.0-beta.5 (May 4, 2022)
### Bug Fixes
- Core: Fix anonymous ID generation ([#18133](https://github.com/storybookjs/storybook/pull/18133))
## 6.5.0-beta.4 (May 4, 2022)
### Features
- UI: Add a parent level toolbar exclusion key for tabs ([#18106](https://github.com/storybookjs/storybook/pull/18106))
- Addon-a11y: Display a11y issues number in addon tab title ([#17983](https://github.com/storybookjs/storybook/pull/17983))
### Bug Fixes
- Addon-docs: Fix Canvas block CURRENT_SELECTION handling ([#18130](https://github.com/storybookjs/storybook/pull/18130))
- Telemetry: Add safecheck for crash reports ([#18129](https://github.com/storybookjs/storybook/pull/18129))
- Addon-a11y: Fix a11y params > element use ([#17989](https://github.com/storybookjs/storybook/pull/17989))
## 6.5.0-beta.3 (May 4, 2022)
### Bug Fixes
- UI: Externalize `react-syntax-highlighter` from components ([#18127](https://github.com/storybookjs/storybook/pull/18127))
## 6.5.0-beta.2 (May 2, 2022)
### Features
- Core: Add optional telemetry and crash reporting ([#18046](https://github.com/storybookjs/storybook/pull/18046))
### Bug Fixes
- Controls: Fix URL deserialization for argTypes with mapping ([#18124](https://github.com/storybookjs/storybook/pull/18124))
- Core: Fix telemetry project root detection ([#18125](https://github.com/storybookjs/storybook/pull/18125))
- React: Fix version detection for older versions of `react-dom` ([#18105](https://github.com/storybookjs/storybook/pull/18105))
- CLI: Add non-monorepo testing tools to exclude lists ([#18092](https://github.com/storybookjs/storybook/pull/18092))
### Maintenance
- Examples: Update example to restore 6.4 auto-title behavior in UI ([#18109](https://github.com/storybookjs/storybook/pull/18109))
- CLI: Remove git.io URL ([#18070](https://github.com/storybookjs/storybook/pull/18070))
- UI: Make panel position a persistent preference ([#18036](https://github.com/storybookjs/storybook/pull/18036))
### Dependency Upgrades
- React: Fix jest-specific-snapshot dev dependency ([#18095](https://github.com/storybookjs/storybook/pull/18095))
## 6.5.0-beta.1 (April 28, 2022)
### Features

View File

@ -15,7 +15,7 @@ This document outlines some of the processes that the maintainers should adhere
| api:(name) | Issue, bug, or pull request related to Storybook's API (e.g.,[makeDecorator](/docs/addons/addons-api.md#makeDecorator-API)) |
| args | Issue, bug, or pull request related to Storybook's [args](/docs/writing-stories/args.md) |
| babel/webpack | Issue, bug, or pull request related to Storybook's build system (e.g., Webpack or Babel), for Webpack 5 issues see below |
| block:(name) | Issue or bug within a certain surface are of Storybook (e.g., [argsTable](/docs/writing-docs/doc-blocks.md#argstable)) |
| block:(name) | Issue or bug within a certain surface are of Storybook (e.g., [argsTable](/docs/writing-docs/doc-block-argstable.md)) |
| BREAKING CHANGE | Issue or pull request that introduces a breaking change within Storybook's ecosystem. |
| BREAKING PRERELASE | Breaking, but only for prerelease users (not relative to the stable release) |
| build-storybook | Issue, bug, or pull request related to Storybook's production build |
@ -24,7 +24,7 @@ This document outlines some of the processes that the maintainers should adhere
| cli | Issue, bug, or pull request that affects the Storybook's CLI |
| compatibility with other tools | Issue, bug, or pull request between Storybook and other tools (e.g., [Nuxt](https://nuxtjs.org/)) |
| components | Issue, bug, or pull request related to Storybook's internal components |
| composition | Issue, bug, or pull request related to Storybook [Composition](/docs/workflows/storybook-composition.md) |
| composition | Issue, bug, or pull request related to Storybook [Composition](/docs/sharing/storybook-composition.md) |
| configuration | Issue, bug, or pull request related to Storybook [configuration](/docs/configure/overview.md) |
| core | Issue, bug, or pull request related to Storybook's Core |
| cra | Issue, bug, or pull request that affects Storybook's compatibility with Create React APP ([CRA](https://create-react-app.dev/docs/getting-started/))|

View File

@ -1,7 +1,9 @@
<h1>Migration</h1>
- [From version 6.4.x to 6.5.0](#from-version-64x-to-650)
- [Vue 3 upgrade](#vue-3-upgrade)
- [React18 new root API](#react18-new-root-api)
- [Renamed isToolshown to showToolbar](#renamed-istoolshown-to-showtoolbar)
- [Deprecated register.js](#deprecated-registerjs)
- [Dropped support for addon-actions addDecorators](#dropped-support-for-addon-actions-adddecorators)
- [Vite builder renamed](#vite-builder-renamed)
@ -10,6 +12,7 @@
- [CSF3 auto-title improvements](#csf3-auto-title-improvements)
- [Auto-title filename case](#auto-title-filename-case)
- [Auto-title redundant filename](#auto-title-redundant-filename)
- [Auto-title always prefixes](#auto-title-always-prefixes)
- [From version 6.3.x to 6.4.0](#from-version-63x-to-640)
- [Automigrate](#automigrate)
- [CRA5 upgrade](#cra5-upgrade)
@ -200,6 +203,10 @@
## From version 6.4.x to 6.5.0
### Vue 3 upgrade
Storybook 6.5 supports Vue 3 out of the box when you install it fresh. However, if you're upgrading your project from a previous version, you'll need to [follow the steps for opting-in to webpack 5](#webpack-5).
### React18 new root API
React 18 introduces a [new root API](https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#updates-to-client-rendering-apis). Starting in 6.5, Storybook for React will auto-detect your react version and use the new root API automatically if you're on React18.
@ -212,6 +219,21 @@ module.exports = {
};
```
### Renamed isToolshown to showToolbar
Storybook's [manager API](docs/addons/addons-api.md) has deprecated the `isToolshown` option (to show/hide the toolbar) and renamed it to `showToolbar` for consistency with other similar UI options.
Example:
```js
// .storybook/manager.js
import { addons } from '@storybook/addons';
addons.setConfig({
showToolbar: false,
});
```
### Deprecated register.js
In ancient versions of Storybook, addons were registered by referring to `addon-name/register.js`. This is going away in SB7.0. Instead you should just add `addon-name` to the `addons` array in `.storybook/main.js`.
@ -288,6 +310,19 @@ This might be considered a breaking change. However, we feel justified to releas
1. We consider it a bug in the initial auto-title implementation
2. CSF3 and the auto-title feature are experimental, and we reserve the right to make breaking changes outside of semver (tho we try to avoid it)
If you want to restore the old titles in the UI, you can customize your sidebar with the following code snippet in `.storybook/manager.js`:
```js
import { addons } from '@storybook/addons';
import startCase from 'lodash/startCase';
addons.setConfig({
sidebar: {
renderLabel: ({ name, type }) => (type === 'story' ? name : startCase(name)),
},
});
```
#### Auto-title redundant filename
The heuristic failed in the common scenario in which each component gets its own directory, e.g. `atoms/Button/Button.stories.js`, which would result in the redundant title `Atoms/Button/Button`. Alternatively, `atoms/Button/index.stories.js` would result in `Atoms/Button/Index`.
@ -301,6 +336,36 @@ Since CSF3 is experimental, we are introducing this technically breaking change
export default { title: 'Atoms/Button/Button' };
```
#### Auto-title always prefixes
When the user provides a `prefix` in their `main.js` `stories` field, it now prefixes all titles to matching stories, whereas in 6.4 and earlier it only prefixed auto-titles.
Consider the following example:
```js
// main.js
module.exports = {
stories: [{ directory: '../src', titlePrefix: 'Custom' }]
}
// ../src/NoTitle.stories.js
export default { component: Foo };
// ../src/Title.stories.js
export default { component: Bar, title: 'Bar' }
```
In 6.4, the final titles would be:
- `NoTitle.stories.js` => `Custom/NoTitle`
- `Title.stories.js` => `Bar`
In 6.5, the final titles would be:
- `NoTitle.stories.js` => `Custom/NoTitle`
- `Title.stories.js` => `Custom/Bar`
<!-- markdown-link-check-disable -->
## From version 6.3.x to 6.4.0
### Automigrate
@ -314,7 +379,9 @@ For example, if you're in a webpack5 project but still use Storybook's default w
You can run the existing suite of automigrations to see which ones apply to your project. This won't update any files unless you accept the changes:
```
npx sb@next automigrate
```
The automigration suite also runs when you create a new project (`sb init`) or when you update storybook (`sb upgrade`).
@ -324,7 +391,9 @@ The automigration suite also runs when you create a new project (`sb init`) or w
Storybook 6.3 supports CRA5 out of the box when you install it fresh. However, if you're upgrading your project from a previous version, you'll need to upgrade the configuration. You can do this automatically by running:
```
npx sb@next automigrate
```
Or you can do the following steps manually to force Storybook to use webpack 5 for building your project:
@ -685,7 +754,29 @@ The `--static-dir` flag has been deprecated and will be removed in Storybook 7.0
### Webpack 5
Storybook 6.3 brings opt-in support for building both your project and the manager UI with webpack 5. To do so:
Storybook 6.3 brings opt-in support for building both your project and the manager UI with webpack 5. To do so, there are two ways:
1 - Upgrade command
If you're upgrading your Storybook version, run this command, which will both upgrade your dependencies but also detect whether you should migrate to webpack5 builders and apply the changes automatically:
```shell
npx sb upgrade
```
2 - Automigrate command
If you don't want to change your Storybook version but want Storybook to detect whether you should migrate to webpack5 builders and apply the changes automatically:
```shell
npx sb automigrate
```
3 - Manually
If either methods did not work or you just want to proceed manually, do the following steps:
Install the dependencies:
```shell
yarn add @storybook/builder-webpack5 @storybook/manager-webpack5 --dev
@ -2153,7 +2244,7 @@ Theming has been rewritten in v5. If you used theming in v4, please consult the
### Story hierarchy defaults
Storybook's UI contains a hierarchical tree of stories that can be configured by `hierarchySeparator` and `hierarchyRootSeparator` [options](./addons/options/README.md).
Storybook's UI contains a hierarchical tree of stories that can be configured by `hierarchySeparator` and `hierarchyRootSeparator` [options](https://github.com/storybookjs/deprecated-addons/blob/master/MIGRATION.md#options-addon-deprecated).
In Storybook 4.x the values defaulted to `null` for both of these options, so that there would be no hierarchy by default.
@ -2818,3 +2909,4 @@ If you **are** using these addons, it takes two steps to migrate:
import { action } from '@storybook/addon-actions';
import { linkTo } from '@storybook/addon-links';
```
<!-- markdown-link-check-enable -->

View File

@ -42,8 +42,8 @@
</a>
</p>
[Storybook](https://storybook.js.org) is a development environment for UI components.
It allows you to browse a component library, view the different states of each component, and interactively develop and test components. Find out more at https://storybook.js.org.
<p align="center">Storybook is a development environment for UI components.<br/>
It allows you to browse a component library, view the different states of each component, and interactively develop and test components.<br/>Find out more at https://storybook.js.org.</p>
<center>
<img src="https://raw.githubusercontent.com/storybookjs/storybook/main/media/storybook-intro.gif" width="100%" />
@ -51,8 +51,9 @@ It allows you to browse a component library, view the different states of each c
<p align="center">
View README for:<br/>
<a href="https://github.com/storybookjs/storybook/blob/main/README.md" title="latest"><img alt="latest" src="https://img.shields.io/npm/v/@storybook/core/latest.svg" /></a>
<a href="https://github.com/storybookjs/storybook/blob/next/README.md" title="next"><img alt="next" src="https://img.shields.io/npm/v/@storybook/core/next.svg" /></a>
<a href="https://github.com/storybookjs/storybook/blob/main/README.md" title="latest"><img alt="latest" src="https://img.shields.io/npm/v/@storybook/core/latest?style=for-the-badge&logo=storybook&logoColor=ffffff&color=66BF3C" /></a>
<a href="https://github.com/storybookjs/storybook/blob/next/README.md" title="next"><img alt="next" src="https://img.shields.io/npm/v/@storybook/core/next?style=for-the-badge&logo=storybook&logoColor=ffffff&color=1EA7FD" /></a>
<a href="https://github.com/storybookjs/storybook/blob/future/base/README.md" title="future"><img alt="future" src="https://img.shields.io/npm/v/@storybook/core-common/future?style=for-the-badge&logo=storybook&logoColor=ffffff&color=FF4785" /></a>
</p>
## Table of contents
@ -80,7 +81,7 @@ Documentation can be found [Storybook's docs site](https://storybook.js.org/docs
### Examples
Here are some featured examples that you can reference to see how Storybook works: <https://storybook.js.org/docs/react/get-started/examples>
Here are some featured examples that you can reference to see how Storybook works: <https://storybook.js.org/showcase>
Storybook comes with a lot of [addons](https://storybook.js.org/docs/react/configure/storybook-addons) for component design, documentation, testing, interactivity, and so on. Storybook's API makes it possible to configure and extend in various ways. It has even been extended to support React Native, Android, iOS, and Flutter development for mobile.
@ -92,19 +93,19 @@ For additional help, join us in the [Storybook Discord](https://discord.gg/story
### Supported Frameworks
| Framework | Demo | |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| [React](app/react) | [v6.4.x](https://storybookjs.netlify.com/official-storybook/?path=/story/*) | [![React](https://img.shields.io/npm/dm/@storybook/react.svg)](app/react) |
| [Vue](app/vue) | [v6.4.x](https://storybookjs.netlify.com/vue-kitchen-sink/) | [![Vue](https://img.shields.io/npm/dm/@storybook/vue.svg)](app/vue) |
| [Angular](app/angular) | [v6.4.x](https://storybookjs.netlify.com/angular-cli/) | [![Angular](https://img.shields.io/npm/dm/@storybook/angular.svg)](app/angular) |
| [Web components](app/web-components) | [v6.4.x](https://storybookjs.netlify.com/web-components-kitchen-sink/) | [![Svelte](https://img.shields.io/npm/dm/@storybook/web-components.svg)](app/web-components) |
| [React Native](https://github.com/storybookjs/react-native) | - | [![React Native](https://img.shields.io/npm/dm/@storybook/react-native.svg)](app/react-native) |
| [HTML](app/html) | [v6.4.x](https://storybookjs.netlify.com/html-kitchen-sink/) | [![HTML](https://img.shields.io/npm/dm/@storybook/html.svg)](app/html) |
| [Ember](app/ember) | [v6.4.x](https://storybookjs.netlify.com/ember-cli/) | [![Ember](https://img.shields.io/npm/dm/@storybook/ember.svg)](app/ember) |
| [Svelte](app/svelte) | [v6.4.x](https://storybookjs.netlify.com/svelte-kitchen-sink/) | [![Svelte](https://img.shields.io/npm/dm/@storybook/svelte.svg)](app/svelte) |
| [Preact](app/preact) | [v6.4.x](https://storybookjs.netlify.com/preact-kitchen-sink/) | [![Preact](https://img.shields.io/npm/dm/@storybook/preact.svg)](app/preact) |
| [Marionette.js](https://github.com/storybookjs/marionette) | - | [![Marionette.js](https://img.shields.io/npm/dm/@storybook/marionette.svg)](app/marionette) |
| [Android, iOS, Flutter](https://github.com/storybookjs/native) | [v6.4.x](https://storybookjs.github.io/native/@storybook/native-flutter-example/index.html) | [![Native](https://img.shields.io/npm/dm/@storybook/native.svg)](https://github.com/storybookjs/native) |
| Framework | Demo | |
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| [React](app/react) | [![Storybook demo](https://img.shields.io/npm/v/@storybook/react/latest?style=flat-square&color=blue&label)](https://storybookjs.netlify.com/official-storybook/?path=/story/*) | [![React](https://img.shields.io/npm/dm/@storybook/react?style=flat-square&color=eee)](app/react) |
| [Vue](app/vue) | [![Storybook demo](https://img.shields.io/npm/v/@storybook/vue/latest?style=flat-square&color=blue&label)](https://storybookjs.netlify.com/vue-kitchen-sink/) | [![Vue](https://img.shields.io/npm/dm/@storybook/vue?style=flat-square&color=eee)](app/vue) |
| [Angular](app/angular) | [![Storybook demo](https://img.shields.io/npm/v/@storybook/angular/latest?style=flat-square&color=blue&label)](https://storybookjs.netlify.com/angular-cli/) | [![Angular](https://img.shields.io/npm/dm/@storybook/angular?style=flat-square&color=eee)](app/angular) |
| [Web components](app/web-components) | [![Storybook demo](https://img.shields.io/npm/v/@storybook/web-components/latest?style=flat-square&color=blue&label)](https://storybookjs.netlify.com/web-components-kitchen-sink/) | [![Svelte](https://img.shields.io/npm/dm/@storybook/web-components?style=flat-square&color=eee)](app/web-components) |
| [React Native](https://github.com/storybookjs/react-native) | - | [![React Native](https://img.shields.io/npm/dm/@storybook/react-native?style=flat-square&color=eee)](https://github.com/storybookjs/react-native) |
| [HTML](app/html) | [![Storybook demo](https://img.shields.io/npm/v/@storybook/html/latest?style=flat-square&color=blue&label)](https://storybookjs.netlify.com/html-kitchen-sink/) | [![HTML](https://img.shields.io/npm/dm/@storybook/html?style=flat-square&color=eee)](app/html) |
| [Ember](app/ember) | [![Storybook demo](https://img.shields.io/npm/v/@storybook/ember/latest?style=flat-square&color=blue&label)](https://storybookjs.netlify.com/ember-cli/) | [![Ember](https://img.shields.io/npm/dm/@storybook/ember?style=flat-square&color=eee)](app/ember) |
| [Svelte](app/svelte) | [![Storybook demo](https://img.shields.io/npm/v/@storybook/svelte/latest?style=flat-square&color=blue&label)](https://storybookjs.netlify.com/svelte-kitchen-sink/) | [![Svelte](https://img.shields.io/npm/dm/@storybook/svelte?style=flat-square&color=eee)](app/svelte) |
| [Preact](app/preact) | [![Storybook demo](https://img.shields.io/npm/v/@storybook/preact/latest?style=flat-square&color=blue&label)](https://storybookjs.netlify.com/preact-kitchen-sink/) | [![Preact](https://img.shields.io/npm/dm/@storybook/preact?style=flat-square&color=eee)](app/preact) |
| [Marionette.js](https://github.com/storybookjs/marionette) | - | [![Marionette.js](https://img.shields.io/npm/dm/@storybook/marionette?style=flat-square&color=eee)](https://github.com/storybookjs/marionette) |
| [Android, iOS, Flutter](https://github.com/storybookjs/native) | [![Storybook demo](https://img.shields.io/npm/v/@storybook/native/latest?style=flat-square&color=blue&label)](https://storybookjs.github.io/native/@storybook/native-flutter-example/index.html) | [![Native](https://img.shields.io/npm/dm/@storybook/native?style=flat-square&color=eee)](https://github.com/storybookjs/native) |
### Sub Projects

View File

@ -81,12 +81,11 @@ there gathering upvotes and "me too" comments. We need a way to make sure that
these bugs get addressed.
For every non-PATCH release, we nominate a small number of bugs that must be
addressed before a release can go out by adding them to the milestone. For example, here's a list of blocking bugs [for the 3.2 milestone](https://github.com/storybookjs/storybook/milestone/3).
addressed before a release can go out by adding them to the milestone. For example, here's a list of blocking bugs [for the 6.5 milestone](https://github.com/storybookjs/storybook/milestone/75).
Adding bugs to the milestone helps people looking for good ways to contribute,
or to understand what is blocking the release so they can actually do something
about it. Discussion about which bugs are critical happens in the `#maintenance`
channel [in our Slack](https://now-examples-slackin-rrirkqohko.now.sh/) [![Storybook Slack](https://now-examples-slackin-rrirkqohko.now.sh/badge.svg)](https://now-examples-slackin-rrirkqohko.now.sh/)
about it. Discussion about which bugs are critical happens in the [`#maintenance` channel](https://discord.com/channels/486522875931656193/490070912448724992) in our Discord Server
If you're experiencing a bug, the best way to make sure that it gets attention
is to upvote it by adding a "thumbs-up" reaction in Github. This way important

View File

@ -2,9 +2,9 @@
## Supported Versions
| Version | Supported |
| ---------- | ------------------ |
| 6.3, 6.4 | :white_check_mark: |
| Version | Supported |
| --------------- | ------------------ |
| 6.3, 6.4, 6.5 | :white_check_mark: |
## Reporting a Vulnerability

View File

@ -15,6 +15,7 @@ function __setMockFiles(newMockFiles) {
const readFile = async (filePath) => mockFiles[filePath];
const readFileSync = (filePath = '') => mockFiles[filePath];
const existsSync = (filePath) => !!mockFiles[filePath];
const readJsonSync = (filePath = '') => JSON.parse(mockFiles[filePath]);
const lstatSync = (filePath) => ({
isFile: () => !!mockFiles[filePath],
});
@ -23,6 +24,7 @@ const lstatSync = (filePath) => ({
fs.__setMockFiles = __setMockFiles;
fs.readFile = readFile;
fs.readFileSync = readFileSync;
fs.readJsonSync = readJsonSync;
fs.existsSync = existsSync;
fs.lstatSync = lstatSync;

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-a11y",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Test component compliance with web accessibility standards",
"keywords": [
"a11y",
@ -45,14 +45,14 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/channels": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/components": "6.5.0-beta.1",
"@storybook/core-events": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/channels": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/components": "6.5.0-rc.1",
"@storybook/core-events": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/theming": "6.5.0-beta.1",
"@storybook/theming": "6.5.0-rc.1",
"axe-core": "^4.2.0",
"core-js": "^3.8.2",
"global": "^4.4.0",
@ -81,7 +81,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/index.js",
"storybook": {
"displayName": "Accessibility",

View File

@ -15,11 +15,6 @@ let active = false;
// Holds latest story we requested a run
let activeStoryId: string | undefined;
const getElement = () => {
const storyRoot = document.getElementById('story-root');
return storyRoot ? storyRoot.childNodes : document.getElementById('root');
};
/**
* Handle A11yContext events.
* Because the event are sent without manual check, we split calls
@ -41,13 +36,14 @@ const run = async (storyId: string) => {
channel.emit(EVENTS.RUNNING);
const axe = (await import('axe-core')).default;
const { element = getElement(), config, options = {} } = input;
const { element = '#root', config, options = {} } = input;
const htmlElement = document.querySelector(element);
axe.reset();
if (config) {
axe.configure(config);
}
const result = await axe.run(element, options);
const result = await axe.run(htmlElement, options);
// It's possible that we requested a new run on a different story.
// Unfortunately, axe doesn't support a cancel method to abort current run.
// We check if the story we run against is still the current one,

View File

@ -63,6 +63,7 @@ describe('A11YPanel', () => {
mockedApi.useStorybookState.mockReset();
mockedApi.useAddonState.mockReset();
mockedApi.useAddonState.mockImplementation((_, defaultState) => React.useState(defaultState));
mockedApi.useChannel.mockReturnValue(jest.fn());
mockedApi.useParameter.mockReturnValue({ manual: false });
const state: Partial<api.State> = { storyId: 'jest' };

View File

@ -53,11 +53,13 @@ describe('A11YPanel', () => {
beforeEach(() => {
mockedApi.useChannel.mockReset();
mockedApi.useStorybookState.mockReset();
mockedApi.useAddonState.mockReset();
mockedApi.useAddonState.mockImplementation((_, defaultState) => React.useState(defaultState));
mockedApi.useChannel.mockReturnValue(jest.fn());
const state: Partial<api.State> = { storyId };
const storyState: Partial<api.State> = { storyId };
// Lazy to mock entire state
mockedApi.useStorybookState.mockReturnValue(state as any);
mockedApi.useStorybookState.mockReturnValue(storyState as any);
});
it('should render children', () => {

View File

@ -1,11 +1,11 @@
import * as React from 'react';
import { themes, convert } from '@storybook/theming';
import { Result } from 'axe-core';
import { useChannel, useStorybookState } from '@storybook/api';
import { useChannel, useStorybookState, useAddonState } from '@storybook/api';
import { STORY_CHANGED, STORY_RENDERED } from '@storybook/core-events';
import { EVENTS } from '../constants';
import { ADDON_ID, EVENTS } from '../constants';
interface Results {
export interface Results {
passes: Result[];
violations: Result[];
incomplete: Result[];
@ -22,9 +22,9 @@ interface A11yContextStore {
}
const colorsByType = [
convert(themes.normal).color.negative, // VIOLATION,
convert(themes.normal).color.positive, // PASS,
convert(themes.normal).color.warning, // INCOMPLETION,
convert(themes.light).color.negative, // VIOLATION,
convert(themes.light).color.positive, // PASS,
convert(themes.light).color.warning, // INCOMPLETION,
];
export const A11yContext = React.createContext<A11yContextStore>({
@ -52,7 +52,7 @@ const defaultResult = {
};
export const A11yContextProvider: React.FC<A11yContextProviderProps> = ({ active, ...props }) => {
const [results, setResults] = React.useState<Results>(defaultResult);
const [results, setResults] = useAddonState<Results>(ADDON_ID, defaultResult);
const [tab, setTab] = React.useState(0);
const [highlighted, setHighlighted] = React.useState<string[]>([]);
const { storyId } = useStorybookState();

View File

@ -0,0 +1,69 @@
import React from 'react';
import { render, fireEvent, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ThemeProvider, themes, convert } from '@storybook/theming';
import { VisionSimulator, baseList } from './VisionSimulator';
const getOptionByNameAndPercentage = (option: string, percentage: number) =>
screen.getByText(
(content, element) =>
content !== '' &&
element.textContent === option &&
(percentage === undefined || element.nextSibling.textContent === `${percentage}% of users`)
);
function ThemedVisionSimulator() {
return (
<ThemeProvider theme={convert(themes.light)}>
<VisionSimulator />
</ThemeProvider>
);
}
describe('Vision Simulator', () => {
it('should render tool button', async () => {
// when
render(<ThemedVisionSimulator />);
// then
// waitFor because WithTooltip is a lazy component
await waitFor(() => expect(screen.getByTitle('Vision simulator')).toBeInTheDocument());
});
it.skip('should display tooltip on click', async () => {
// given
render(<ThemedVisionSimulator />);
await waitFor(() => expect(screen.getByTitle('Vision simulator')).toBeInTheDocument());
// when
userEvent.click(screen.getByRole('button', { name: 'Vision simulator' }));
// then
await waitFor(() => expect(screen.getByText('blurred vision')).toBeInTheDocument());
baseList.forEach(({ name, percentage }) =>
expect(getOptionByNameAndPercentage(name, percentage)).toBeInTheDocument()
);
});
it.skip('should set filter', async () => {
// given
render(<ThemedVisionSimulator />);
await waitFor(() => expect(screen.getByTitle('Vision simulator')).toBeInTheDocument());
userEvent.click(screen.getByRole('button', { name: 'Vision simulator' }));
await waitFor(() => expect(screen.getByText('blurred vision')).toBeInTheDocument());
// when
fireEvent.click(screen.getByText('blurred vision'));
// then
// eslint-disable-next-line no-undef
const rule = Object.values(document.styleSheets)
.filter(({ cssRules }) => cssRules)
.map(({ cssRules }) => Object.values(cssRules))
.flat()
.find((cssRule: CSSRule) => cssRule.selectorText === '#storybook-preview-iframe');
expect(rule).toBeDefined();
expect(rule.style.filter).toBe('blur(2px)');
});
});

View File

@ -1,4 +1,4 @@
import React, { FunctionComponent, ReactNode, useState } from 'react';
import React, { ReactNode, useState } from 'react';
import { Global, styled } from '@storybook/theming';
import { Icons, IconButton, WithTooltip, TooltipLinkList } from '@storybook/components';
@ -6,32 +6,37 @@ import { Filters } from './ColorFilters';
const iframeId = 'storybook-preview-iframe';
const baseList = [
'blurred vision',
'deuteranomaly',
'deuteranopia',
'protanomaly',
'protanopia',
'tritanomaly',
'tritanopia',
'achromatomaly',
'achromatopsia',
'grayscale',
] as const;
interface Option {
name: string;
percentage?: number;
}
type Filter = typeof baseList[number] | null;
export const baseList = [
{ name: 'blurred vision', percentage: 22.9 },
{ name: 'deuteranomaly', percentage: 2.7 },
{ name: 'deuteranopia', percentage: 0.56 },
{ name: 'protanomaly', percentage: 0.66 },
{ name: 'protanopia', percentage: 0.59 },
{ name: 'tritanomaly', percentage: 0.01 },
{ name: 'tritanopia', percentage: 0.016 },
{ name: 'achromatomaly', percentage: 0.00001 },
{ name: 'achromatopsia', percentage: 0.0001 },
{ name: 'grayscale' },
] as Option[];
const getFilter = (filter: Filter) => {
if (!filter) {
type Filter = Option | null;
const getFilter = (filterName: string) => {
if (!filterName) {
return 'none';
}
if (filter === 'blurred vision') {
if (filterName === 'blurred vision') {
return 'blur(2px)';
}
if (filter === 'grayscale') {
if (filterName === 'grayscale') {
return 'grayscale(100%)';
}
return `url('#${filter}')`;
return `url('#${filterName}')`;
};
const Hidden = styled.div(() => ({
@ -42,7 +47,7 @@ const Hidden = styled.div(() => ({
},
}));
const ColorIcon = styled.span<{ filter: Filter }>(
const ColorIcon = styled.span<{ filter: string }>(
{
background: 'linear-gradient(to right, #F44336, #FF9800, #FFEB3B, #8BC34A, #2196F3, #9C27B0)',
borderRadius: '1rem',
@ -66,6 +71,20 @@ export interface Link {
onClick: () => void;
}
const Column = styled.span({
display: 'flex',
flexDirection: 'column',
});
const Title = styled.span({
textTransform: 'capitalize',
});
const Description = styled.span(({ theme }) => ({
fontSize: 11,
color: theme.textMutedColor,
}));
const getColorList = (active: Filter, set: (i: Filter) => void): Link[] => [
...(active !== null
? [
@ -80,27 +99,34 @@ const getColorList = (active: Filter, set: (i: Filter) => void): Link[] => [
},
]
: []),
...baseList.map((i) => ({
id: i,
title: i.charAt(0).toUpperCase() + i.slice(1),
onClick: () => {
set(i);
},
right: <ColorIcon filter={i} />,
active: active === i,
})),
...baseList.map((i) => {
const description = i.percentage !== undefined ? `${i.percentage}% of users` : undefined;
return {
id: i.name,
title: (
<Column>
<Title>{i.name}</Title>
{description && <Description>{description}</Description>}
</Column>
),
onClick: () => {
set(i);
},
right: <ColorIcon filter={i.name} />,
active: active === i,
};
}),
];
export const VisionSimulator: FunctionComponent = () => {
export const VisionSimulator = () => {
const [filter, setFilter] = useState<Filter>(null);
return (
<>
{filter && (
<Global
styles={{
[`#${iframeId}`]: {
filter: getFilter(filter),
filter: getFilter(filter.name),
},
}}
/>

View File

@ -0,0 +1,55 @@
import { addons } from '@storybook/addons';
import * as api from '@storybook/api';
import { PANEL_ID } from './constants';
import './manager';
jest.mock('@storybook/api');
jest.mock('@storybook/addons');
const mockedApi = api as unknown as jest.Mocked<api.API>;
mockedApi.getAddonState = jest.fn();
const mockedAddons = addons as jest.Mocked<typeof addons>;
const registrationImpl = mockedAddons.register.mock.calls[0][1];
describe('A11yManager', () => {
it('should register the panels', () => {
// when
registrationImpl(mockedApi);
// then
expect(mockedAddons.add.mock.calls).toHaveLength(2);
expect(mockedAddons.add).toHaveBeenCalledWith(PANEL_ID, expect.anything());
const panel = mockedAddons.add.mock.calls
.map(([_, def]) => def)
.find(({ type }) => type === 'panel');
const tool = mockedAddons.add.mock.calls
.map(([_, def]) => def)
.find(({ type }) => type === 'tool');
expect(panel).toBeDefined();
expect(tool).toBeDefined();
});
it('should compute title with no issues', () => {
// given
mockedApi.getAddonState.mockImplementation(() => undefined);
registrationImpl(api as unknown as api.API);
const title = mockedAddons.add.mock.calls
.map(([_, def]) => def)
.find(({ type }) => type === 'panel').title as Function;
// when / then
expect(title()).toBe('Accessibility');
});
it('should compute title with issues', () => {
// given
mockedApi.getAddonState.mockImplementation(() => ({ violations: [{}], incomplete: [{}, {}] }));
registrationImpl(mockedApi);
const title = mockedAddons.add.mock.calls
.map(([_, def]) => def)
.find(({ type }) => type === 'panel').title as Function;
// when / then
expect(title()).toBe('Accessibility (3)');
});
});

View File

@ -3,9 +3,9 @@ import { addons, types } from '@storybook/addons';
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants';
import { VisionSimulator } from './components/VisionSimulator';
import { A11YPanel } from './components/A11YPanel';
import { A11yContextProvider } from './components/A11yContext';
import { A11yContextProvider, Results } from './components/A11yContext';
addons.register(ADDON_ID, () => {
addons.register(ADDON_ID, (api) => {
addons.add(PANEL_ID, {
title: '',
type: types.TOOL,
@ -14,7 +14,13 @@ addons.register(ADDON_ID, () => {
});
addons.add(PANEL_ID, {
title: 'Accessibility',
title() {
const addonState: Results = api?.getAddonState(ADDON_ID);
const violationsNb = addonState?.violations?.length || 0;
const incompleteNb = addonState?.incomplete?.length || 0;
const totalNb = violationsNb + incompleteNb;
return totalNb !== 0 ? `Accessibility (${totalNb})` : 'Accessibility';
},
type: types.PANEL,
render: ({ active = true, key }) => (
<A11yContextProvider key={key} active={active}>

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-actions",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Get UI feedback when an action is performed on an interactive element",
"keywords": [
"storybook",
@ -41,13 +41,13 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/components": "6.5.0-beta.1",
"@storybook/core-events": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/components": "6.5.0-rc.1",
"@storybook/core-events": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/theming": "6.5.0-beta.1",
"@storybook/theming": "6.5.0-rc.1",
"core-js": "^3.8.2",
"fast-deep-equal": "^3.1.3",
"global": "^4.4.0",
@ -56,7 +56,7 @@
"prop-types": "^15.7.2",
"react-inspector": "^5.1.0",
"regenerator-runtime": "^0.13.7",
"telejson": "^5.3.3",
"telejson": "^6.0.8",
"ts-dedent": "^2.0.0",
"util-deprecate": "^1.0.2",
"uuid-browser": "^3.1.0"
@ -80,7 +80,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/index.js",
"storybook": {
"displayName": "Actions",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-backgrounds",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Switch backgrounds to view components in different settings",
"keywords": [
"addon",
@ -45,13 +45,13 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/components": "6.5.0-beta.1",
"@storybook/core-events": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/components": "6.5.0-rc.1",
"@storybook/core-events": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/theming": "6.5.0-beta.1",
"@storybook/theming": "6.5.0-rc.1",
"core-js": "^3.8.2",
"global": "^4.4.0",
"memoizerific": "^1.11.3",
@ -77,7 +77,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/index.js",
"storybook": {
"displayName": "Backgrounds",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-controls",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Interact with component inputs dynamically in the Storybook UI",
"keywords": [
"addon",
@ -45,15 +45,15 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/components": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/components": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/node-logger": "6.5.0-beta.1",
"@storybook/store": "6.5.0-beta.1",
"@storybook/theming": "6.5.0-beta.1",
"@storybook/node-logger": "6.5.0-rc.1",
"@storybook/store": "6.5.0-rc.1",
"@storybook/theming": "6.5.0-rc.1",
"core-js": "^3.8.2",
"lodash": "^4.17.21",
"ts-dedent": "^2.0.0"
@ -73,7 +73,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/manager.js",
"storybook": {
"displayName": "Controls",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-docs",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Document component usage and properties in Markdown",
"keywords": [
"addon",
@ -59,20 +59,20 @@
"@babel/preset-env": "^7.12.11",
"@jest/transform": "^26.6.2",
"@mdx-js/react": "^1.6.22",
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/components": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/core-events": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/components": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/core-events": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/docs-tools": "6.5.0-beta.1",
"@storybook/mdx1-csf": "canary",
"@storybook/node-logger": "6.5.0-beta.1",
"@storybook/postinstall": "6.5.0-beta.1",
"@storybook/preview-web": "6.5.0-beta.1",
"@storybook/source-loader": "6.5.0-beta.1",
"@storybook/store": "6.5.0-beta.1",
"@storybook/theming": "6.5.0-beta.1",
"@storybook/docs-tools": "6.5.0-rc.1",
"@storybook/mdx1-csf": "^0.0.1",
"@storybook/node-logger": "6.5.0-rc.1",
"@storybook/postinstall": "6.5.0-rc.1",
"@storybook/preview-web": "6.5.0-rc.1",
"@storybook/source-loader": "6.5.0-rc.1",
"@storybook/store": "6.5.0-rc.1",
"@storybook/theming": "6.5.0-rc.1",
"babel-loader": "^8.0.0",
"core-js": "^3.8.2",
"fast-deep-equal": "^3.1.3",
@ -86,11 +86,11 @@
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@storybook/mdx2-csf": "canary",
"@storybook/mdx2-csf": "^0.0.3",
"@types/util-deprecate": "^1.0.0"
},
"peerDependencies": {
"@storybook/mdx2-csf": "*",
"@storybook/mdx2-csf": "^0.0.3",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
@ -108,7 +108,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/index.js",
"storybook": {
"displayName": "Docs",

View File

@ -11,6 +11,7 @@ import { DocsContext, DocsContextProps } from './DocsContext';
import { SourceContext, SourceContextProps } from './SourceContainer';
import { getSourceProps, SourceState } from './Source';
import { useStories } from './useStory';
import { CURRENT_SELECTION } from './types';
export { SourceState };
@ -53,7 +54,10 @@ const getPreviewProps = (
);
const sourceProps = getSourceProps({ ids: targetIds }, docsContext, sourceContext);
if (!sourceState) sourceState = sourceProps.state;
const stories = useStories(targetIds, docsContext);
const storyIds = targetIds.map((targetId) =>
targetId === CURRENT_SELECTION ? docsContext.id : targetId
);
const stories = useStories(storyIds, docsContext);
isLoading = stories.some((s) => !s);
return {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-essentials",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Curated addons to bring out the best of Storybook",
"keywords": [
"addon",
@ -39,25 +39,25 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addon-actions": "6.5.0-beta.1",
"@storybook/addon-backgrounds": "6.5.0-beta.1",
"@storybook/addon-controls": "6.5.0-beta.1",
"@storybook/addon-docs": "6.5.0-beta.1",
"@storybook/addon-measure": "6.5.0-beta.1",
"@storybook/addon-outline": "6.5.0-beta.1",
"@storybook/addon-toolbars": "6.5.0-beta.1",
"@storybook/addon-viewport": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/node-logger": "6.5.0-beta.1",
"@storybook/addon-actions": "6.5.0-rc.1",
"@storybook/addon-backgrounds": "6.5.0-rc.1",
"@storybook/addon-controls": "6.5.0-rc.1",
"@storybook/addon-docs": "6.5.0-rc.1",
"@storybook/addon-measure": "6.5.0-rc.1",
"@storybook/addon-outline": "6.5.0-rc.1",
"@storybook/addon-toolbars": "6.5.0-rc.1",
"@storybook/addon-viewport": "6.5.0-rc.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/node-logger": "6.5.0-rc.1",
"core-js": "^3.8.2",
"regenerator-runtime": "^0.13.7",
"ts-dedent": "^2.0.0"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@storybook/vue": "6.5.0-beta.1",
"@storybook/vue": "6.5.0-rc.1",
"@types/jest": "^26.0.16",
"@types/webpack-env": "^1.16.0"
},
@ -120,6 +120,6 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/index.js"
}

View File

@ -41,22 +41,25 @@ Interactions relies on "instrumented" versions of Jest and Testing Library, that
`@storybook/testing-library` instead of their original package. You can then use these libraries in your `play` function.
```js
import { Button } from './Button';
import { expect } from '@storybook/jest';
import { within, userEvent } from '@storybook/testing-library';
export default {
title: 'Button',
component: Button,
argTypes: {
onClick: { action: true },
},
};
export const Demo = {
play: async ({ args, canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole('button'));
await expect(args.onClick).toHaveBeenCalled();
},
const Template = (args) => <Button {...args} />;
export const Demo = Template.bind({});
Demo.play = async ({ args, canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole('button'));
await expect(args.onClick).toHaveBeenCalled();
};
```

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-interactions",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Automate, test and debug user interactions",
"keywords": [
"storybook-addons",
@ -42,15 +42,15 @@
},
"dependencies": {
"@devtools-ds/object-inspector": "^1.1.2",
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/components": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/core-events": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/components": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/core-events": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/instrumenter": "6.5.0-beta.1",
"@storybook/theming": "6.5.0-beta.1",
"@storybook/instrumenter": "6.5.0-rc.1",
"@storybook/theming": "6.5.0-rc.1",
"core-js": "^3.8.2",
"global": "^4.4.0",
"jest-mock": "^27.0.6",
@ -59,7 +59,7 @@
},
"devDependencies": {
"@storybook/jest": "^0.0.5",
"@storybook/testing-library": "^0.0.7",
"@storybook/testing-library": "0.0.14-next.0",
"formik": "^2.2.9"
},
"peerDependencies": {
@ -77,7 +77,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/index.js",
"storybook": {
"displayName": "Interactions",

View File

@ -4,7 +4,7 @@ import { ComponentStoryObj, ComponentMeta } from '@storybook/react';
import { CallStates } from '@storybook/instrumenter';
import { styled } from '@storybook/theming';
import { getCall } from './mocks';
import { getCalls, getInteractions } from './mocks';
import { AddonPanelPure } from './Panel';
import SubnavStories from './components/Subnav/Subnav.stories';
@ -20,6 +20,8 @@ const StyledWrapper = styled.div(({ theme }) => ({
overflow: 'auto',
}));
const interactions = getInteractions(CallStates.DONE);
export default {
title: 'Addons/Interactions/Panel',
component: AddonPanelPure,
@ -34,10 +36,10 @@ export default {
layout: 'fullscreen',
},
args: {
calls: new Map(),
calls: new Map(getCalls(CallStates.DONE).map((call) => [call.id, call])),
controls: SubnavStories.args.controls,
controlStates: SubnavStories.args.controlStates,
interactions: [getCall(CallStates.DONE)],
interactions,
fileName: 'addon-interactions.stories.tsx',
hasException: false,
isPlaying: false,
@ -52,14 +54,14 @@ type Story = ComponentStoryObj<typeof AddonPanelPure>;
export const Passing: Story = {
args: {
interactions: [getCall(CallStates.DONE)],
interactions: getInteractions(CallStates.DONE),
},
};
export const Paused: Story = {
args: {
isPlaying: true,
interactions: [getCall(CallStates.WAITING)],
interactions: getInteractions(CallStates.WAITING),
controlStates: {
debugger: true,
start: false,
@ -68,20 +70,21 @@ export const Paused: Story = {
next: true,
end: true,
},
pausedAt: interactions[interactions.length - 1].id,
},
};
export const Playing: Story = {
args: {
isPlaying: true,
interactions: [getCall(CallStates.ACTIVE)],
interactions: getInteractions(CallStates.ACTIVE),
},
};
export const Failed: Story = {
args: {
hasException: true,
interactions: [getCall(CallStates.ERROR)],
interactions: getInteractions(CallStates.ERROR),
},
};

View File

@ -28,10 +28,16 @@ interface InteractionsPanelProps {
active: boolean;
controls: Controls;
controlStates: ControlStates;
interactions: (Call & { status?: CallStates })[];
interactions: (Call & {
status?: CallStates;
childCallIds: Call['id'][];
isCollapsed: boolean;
toggleCollapsed: () => void;
})[];
fileName?: string;
hasException?: boolean;
isPlaying?: boolean;
pausedAt?: Call['id'];
calls: Map<string, any>;
endRef?: React.Ref<HTMLDivElement>;
onScrollToEnd?: () => void;
@ -66,6 +72,7 @@ export const AddonPanelPure: React.FC<InteractionsPanelProps> = React.memo(
fileName,
hasException,
isPlaying,
pausedAt,
onScrollToEnd,
endRef,
isRerunAnimating,
@ -87,15 +94,21 @@ export const AddonPanelPure: React.FC<InteractionsPanelProps> = React.memo(
setIsRerunAnimating={setIsRerunAnimating}
/>
)}
{interactions.map((call) => (
<Interaction
key={call.id}
call={call}
callsById={calls}
controls={controls}
controlStates={controlStates}
/>
))}
<div>
{interactions.map((call) => (
<Interaction
key={call.id}
call={call}
callsById={calls}
controls={controls}
controlStates={controlStates}
childCallIds={call.childCallIds}
isCollapsed={call.isCollapsed}
toggleCollapsed={call.toggleCollapsed}
pausedAt={pausedAt}
/>
))}
</div>
<div ref={endRef} />
{!isPlaying && interactions.length === 0 && (
<Placeholder>
@ -116,17 +129,17 @@ export const AddonPanelPure: React.FC<InteractionsPanelProps> = React.memo(
export const Panel: React.FC<AddonPanelProps> = (props) => {
const [storyId, setStoryId] = React.useState<StoryId>();
const [controlStates, setControlStates] = React.useState<ControlStates>(INITIAL_CONTROL_STATES);
const [pausedAt, setPausedAt] = React.useState<Call['id']>();
const [isPlaying, setPlaying] = React.useState(false);
const [isRerunAnimating, setIsRerunAnimating] = React.useState(false);
const [scrollTarget, setScrollTarget] = React.useState<HTMLElement>();
const [collapsed, setCollapsed] = React.useState<Set<Call['id']>>(new Set());
const [log, setLog] = React.useState<LogItem[]>([]);
// Calls are tracked in a ref so we don't needlessly rerender.
const calls = React.useRef<Map<Call['id'], Omit<Call, 'status'>>>(new Map());
const setCall = ({ status, ...call }: Call) => calls.current.set(call.id, call);
const [log, setLog] = React.useState<LogItem[]>([]);
const interactions = log.map(({ callId, status }) => ({ ...calls.current.get(callId), status }));
const endRef = React.useRef();
React.useEffect(() => {
let observer: IntersectionObserver;
@ -146,10 +159,12 @@ export const Panel: React.FC<AddonPanelProps> = (props) => {
[EVENTS.SYNC]: (payload) => {
setControlStates(payload.controlStates);
setLog(payload.logItems);
setPausedAt(payload.pausedAt);
},
[STORY_RENDER_PHASE_CHANGED]: (event) => {
setStoryId(event.storyId);
setPlaying(event.newPhase === 'playing');
setPausedAt(undefined);
},
},
[]
@ -177,6 +192,38 @@ export const Panel: React.FC<AddonPanelProps> = (props) => {
const showStatus = log.length > 0 && !isPlaying;
const hasException = log.some((item) => item.status === CallStates.ERROR);
const interactions = React.useMemo(() => {
const callsById = new Map<Call['id'], Call>();
const childCallMap = new Map<Call['id'], Call['id'][]>();
return log
.filter(({ callId, parentId }) => {
if (!parentId) return true;
childCallMap.set(parentId, (childCallMap.get(parentId) || []).concat(callId));
return !collapsed.has(parentId);
})
.map(({ callId, status }) => ({ ...calls.current.get(callId), status } as Call))
.map((call) => {
const status =
call.status === CallStates.ERROR &&
callsById.get(call.parentId)?.status === CallStates.ACTIVE
? CallStates.ACTIVE
: call.status;
callsById.set(call.id, { ...call, status });
return {
...call,
status,
childCallIds: childCallMap.get(call.id),
isCollapsed: collapsed.has(call.id),
toggleCollapsed: () =>
setCollapsed((ids) => {
if (ids.has(call.id)) ids.delete(call.id);
else ids.add(call.id);
return new Set(ids);
}),
};
});
}, [log, collapsed]);
return (
<React.Fragment key="interactions">
<TabStatus>
@ -191,6 +238,7 @@ export const Panel: React.FC<AddonPanelProps> = (props) => {
fileName={fileName}
hasException={hasException}
isPlaying={isPlaying}
pausedAt={pausedAt}
endRef={endRef}
onScrollToEnd={scrollTarget && scrollToTarget}
isRerunAnimating={isRerunAnimating}

View File

@ -35,6 +35,12 @@ export const Demo: CSF2Story = (args) => (
Demo.play = async ({ args, canvasElement }) => {
await userEvent.click(within(canvasElement).getByRole('button'));
await expect(args.onSubmit).toHaveBeenCalledWith(expect.stringMatching(/([A-Z])\w+/gi));
await expect([{ name: 'John', age: 42 }]).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: 'John' }),
expect.objectContaining({ age: 42 }),
])
);
};
export const FindBy: CSF2Story = (args) => {
@ -131,7 +137,7 @@ export const StandardEmailFailed: CSF3Story = {
await userEvent.click(canvas.getByRole('button', { name: /create account/i }));
await canvas.findByText('Please enter a correctly formatted email address');
expect(args.onSubmit).not.toHaveBeenCalled();
await expect(args.onSubmit).not.toHaveBeenCalled();
},
};

View File

@ -2,7 +2,7 @@ import { ComponentStoryObj, ComponentMeta } from '@storybook/react';
import { expect } from '@storybook/jest';
import { CallStates } from '@storybook/instrumenter';
import { userEvent, within } from '@storybook/testing-library';
import { getCall } from '../../mocks';
import { getCalls } from '../../mocks';
import { Interaction } from './Interaction';
import SubnavStories from '../Subnav/Subnav.stories';
@ -13,7 +13,7 @@ export default {
title: 'Addons/Interactions/Interaction',
component: Interaction,
args: {
callsById: new Map(),
callsById: new Map(getCalls(CallStates.DONE).map((call) => [call.id, call])),
controls: SubnavStories.args.controls,
controlStates: SubnavStories.args.controlStates,
},
@ -21,25 +21,31 @@ export default {
export const Active: Story = {
args: {
call: getCall(CallStates.ACTIVE),
call: getCalls(CallStates.ACTIVE).slice(-1)[0],
},
};
export const Waiting: Story = {
args: {
call: getCall(CallStates.WAITING),
call: getCalls(CallStates.WAITING).slice(-1)[0],
},
};
export const Failed: Story = {
args: {
call: getCall(CallStates.ERROR),
call: getCalls(CallStates.ERROR).slice(-1)[0],
},
};
export const Done: Story = {
args: {
call: getCall(CallStates.DONE),
call: getCalls(CallStates.DONE).slice(-1)[0],
},
};
export const WithParent: Story = {
args: {
call: { ...getCalls(CallStates.DONE).slice(-1)[0], parentId: 'parent-id' },
},
};

View File

@ -1,4 +1,5 @@
import * as React from 'react';
import { IconButton, Icons, TooltipNote, WithTooltip } from '@storybook/components';
import { Call, CallStates, ControlStates } from '@storybook/instrumenter';
import { styled, typography } from '@storybook/theming';
import { transparentize } from 'polished';
@ -15,23 +16,55 @@ const MethodCallWrapper = styled.div(() => ({
inlineSize: 'calc( 100% - 40px )',
}));
const RowContainer = styled('div', { shouldForwardProp: (prop) => !['call'].includes(prop) })<{
call: Call;
}>(({ theme, call }) => ({
display: 'flex',
flexDirection: 'column',
borderBottom: `1px solid ${theme.appBorderColor}`,
fontFamily: typography.fonts.base,
fontSize: 13,
...(call.status === CallStates.ERROR && {
backgroundColor:
theme.base === 'dark' ? transparentize(0.93, theme.color.negative) : theme.background.warning,
const RowContainer = styled('div', {
shouldForwardProp: (prop) => !['call', 'pausedAt'].includes(prop),
})<{ call: Call; pausedAt: Call['id'] }>(
({ theme, call }) => ({
position: 'relative',
display: 'flex',
flexDirection: 'column',
borderBottom: `1px solid ${theme.appBorderColor}`,
fontFamily: typography.fonts.base,
fontSize: 13,
...(call.status === CallStates.ERROR && {
backgroundColor:
theme.base === 'dark'
? transparentize(0.93, theme.color.negative)
: theme.background.warning,
}),
paddingLeft: call.parentId ? 20 : 0,
}),
({ theme, call, pausedAt }) =>
pausedAt === call.id && {
'&::before': {
content: '""',
position: 'absolute',
top: -5,
zIndex: 1,
borderTop: '4.5px solid transparent',
borderLeft: `7px solid ${theme.color.warning}`,
borderBottom: '4.5px solid transparent',
},
'&::after': {
content: '""',
position: 'absolute',
top: -1,
zIndex: 1,
width: '100%',
borderTop: `1.5px solid ${theme.color.warning}`,
},
}
);
const RowHeader = styled.div<{ disabled: boolean }>(({ theme, disabled }) => ({
display: 'flex',
'&:hover': disabled ? {} : { background: theme.background.hoverable },
}));
const RowLabel = styled('button', { shouldForwardProp: (prop) => !['call'].includes(prop) })<
React.ButtonHTMLAttributes<HTMLButtonElement> & { call: Call }
>(({ theme, disabled, call }) => ({
flex: 1,
display: 'grid',
background: 'none',
border: 0,
@ -42,7 +75,6 @@ const RowLabel = styled('button', { shouldForwardProp: (prop) => !['call'].inclu
padding: '8px 15px',
textAlign: 'start',
cursor: disabled || call.status === CallStates.ERROR ? 'default' : 'pointer',
'&:hover': disabled ? {} : { background: theme.background.hoverable },
'&:focus-visible': {
outline: 0,
boxShadow: `inset 3px 0 0 0 ${
@ -55,45 +87,101 @@ const RowLabel = styled('button', { shouldForwardProp: (prop) => !['call'].inclu
},
}));
const RowMessage = styled('pre')({
margin: 0,
padding: '8px 10px 8px 30px',
const RowActions = styled.div(({ theme }) => ({
padding: 6,
}));
export const StyledIconButton = styled(IconButton as any)(({ theme }) => ({
color: theme.color.mediumdark,
margin: '0 3px',
}));
const Note = styled(TooltipNote)(({ theme }) => ({
fontFamily: theme.typography.fonts.base,
}));
const RowMessage = styled('div')(({ theme }) => ({
padding: '8px 10px 8px 36px',
fontSize: typography.size.s1,
});
pre: {
margin: 0,
padding: 0,
},
p: {
color: theme.color.dark,
},
}));
const Exception = ({ exception }: { exception: Call['exception'] }) => {
if (exception.message.startsWith('expect(')) {
return <MatcherResult {...exception} />;
}
const paragraphs = exception.message.split('\n\n');
const more = paragraphs.length > 1;
return (
<RowMessage>
<pre>{paragraphs[0]}</pre>
{more && <p>See the full stack trace in the browser console.</p>}
</RowMessage>
);
};
export const Interaction = ({
call,
callsById,
controls,
controlStates,
childCallIds,
isCollapsed,
toggleCollapsed,
pausedAt,
}: {
call: Call;
callsById: Map<Call['id'], Call>;
controls: Controls;
controlStates: ControlStates;
childCallIds?: Call['id'][];
isCollapsed: boolean;
toggleCollapsed: () => void;
pausedAt?: Call['id'];
}) => {
const [isHovered, setIsHovered] = React.useState(false);
return (
<RowContainer call={call}>
<RowLabel
call={call}
onClick={() => controls.goto(call.id)}
disabled={!controlStates.goto}
onMouseEnter={() => controlStates.goto && setIsHovered(true)}
onMouseLeave={() => controlStates.goto && setIsHovered(false)}
>
<StatusIcon status={isHovered ? CallStates.ACTIVE : call.status} />
<MethodCallWrapper style={{ marginLeft: 6, marginBottom: 1 }}>
<MethodCall call={call} callsById={callsById} />
</MethodCallWrapper>
</RowLabel>
{call.status === CallStates.ERROR &&
call.exception &&
(call.exception.message.startsWith('expect(') ? (
<MatcherResult {...call.exception} />
) : (
<RowMessage>{call.exception.message}</RowMessage>
))}
<RowContainer call={call} pausedAt={pausedAt}>
<RowHeader disabled={!controlStates.goto || !call.interceptable || !!call.parentId}>
<RowLabel
call={call}
onClick={() => controls.goto(call.id)}
disabled={!controlStates.goto || !call.interceptable || !!call.parentId}
onMouseEnter={() => controlStates.goto && setIsHovered(true)}
onMouseLeave={() => controlStates.goto && setIsHovered(false)}
>
<StatusIcon status={isHovered ? CallStates.ACTIVE : call.status} />
<MethodCallWrapper style={{ marginLeft: 6, marginBottom: 1 }}>
<MethodCall call={call} callsById={callsById} />
</MethodCallWrapper>
</RowLabel>
<RowActions>
{childCallIds?.length > 0 && (
<WithTooltip
hasChrome={false}
tooltip={
<Note
note={`${isCollapsed ? 'Show' : 'Hide'} interactions (${childCallIds.length})`}
/>
}
>
<StyledIconButton containsIcon onClick={toggleCollapsed}>
<Icons icon="listunordered" />
</StyledIconButton>
</WithTooltip>
)}
</RowActions>
</RowHeader>
{call.status === CallStates.ERROR && call.exception?.callId === call.id && (
<Exception exception={call.exception} />
)}
</RowContainer>
);
};

View File

@ -12,9 +12,9 @@ const ListWrapper = styled.ul({
const Wrapper = styled.div({
display: 'flex',
width: '100%',
borderBottom: `1px solid ${convert(themes.normal).appBorderColor}`,
borderBottom: `1px solid ${convert(themes.light).appBorderColor}`,
'&:hover': {
background: convert(themes.normal).background.hoverable,
background: convert(themes.light).background.hoverable,
},
});
@ -22,7 +22,7 @@ const Icon = styled(Icons)<IconsProps>({
height: 10,
width: 10,
minWidth: 10,
color: convert(themes.normal).color.mediumdark,
color: convert(themes.light).color.mediumdark,
marginRight: 10,
transition: 'transform 0.1s ease-in-out',
alignSelf: 'center',
@ -30,8 +30,8 @@ const Icon = styled(Icons)<IconsProps>({
});
const HeaderBar = styled.div({
padding: convert(themes.normal).layoutMargin,
paddingLeft: convert(themes.normal).layoutMargin - 3,
padding: convert(themes.light).layoutMargin,
paddingLeft: convert(themes.light).layoutMargin - 3,
background: 'none',
color: 'inherit',
textAlign: 'left',
@ -41,13 +41,13 @@ const HeaderBar = styled.div({
'&:focus': {
outline: '0 none',
borderLeft: `3px solid ${convert(themes.normal).color.secondary}`,
borderLeft: `3px solid ${convert(themes.light).color.secondary}`,
},
});
const Description = styled.div({
padding: convert(themes.normal).layoutMargin,
marginBottom: convert(themes.normal).layoutMargin,
padding: convert(themes.light).layoutMargin,
marginBottom: convert(themes.light).layoutMargin,
fontStyle: 'italic',
});
@ -69,7 +69,7 @@ export const ListItem: React.FC<ListItemProps> = ({ item }) => {
<HeaderBar onClick={() => onToggle(!open)} role="button">
<Icon
icon="chevrondown"
color={convert(themes.normal).appBorderColor}
color={convert(themes.light).appBorderColor}
style={{
transform: `rotate(${open ? 0 : -90}deg)`,
}}

View File

@ -50,7 +50,7 @@ export const MatcherResult = ({ message }: { message: string }) => {
<pre
style={{
margin: 0,
padding: '8px 10px 8px 30px',
padding: '8px 10px 8px 36px',
fontSize: typography.size.s1,
}}
>

View File

@ -27,7 +27,6 @@ export default {
},
};
class FooBar {}
export const Args = () => (
<div style={{ display: 'inline-flex', flexDirection: 'column', gap: 10 }}>
<Node value={null} />
@ -56,37 +55,49 @@ export const Args = () => (
}}
showObjectInspector
/>
<Node value={new FooBar()} />
<Node value={function goFaster() {}} />
<Node value={{ __class__: { name: 'FooBar' } }} />
<Node value={{ __function__: { name: 'goFaster' } }} />
<Node value={{ __element__: { localName: 'hr' } }} />
<Node value={{ __element__: { localName: 'foo', prefix: 'x' } }} />
<Node value={{ __element__: { localName: 'div', id: 'foo' } }} />
<Node value={{ __element__: { localName: 'span', classNames: ['foo', 'bar'] } }} />
<Node value={{ __element__: { localName: 'button', innerText: 'Click me' } }} />
<Node value={new Date(Date.UTC(2012, 11, 20, 0, 0, 0))} />
<Node value={new Date(1600000000000)} />
<Node value={new Date(1600000000123)} />
<Node value={new EvalError()} />
<Node value={new SyntaxError("Can't do that")} />
<Node value={new TypeError("Cannot read property 'foo' of undefined")} />
<Node value={new ReferenceError('Invalid left-hand side in assignment')} />
<Node
value={
new Error(
"XMLHttpRequest cannot load https://example.com. No 'Access-Control-Allow-Origin' header is present on the requested resource."
)
}
value={{ __date__: { value: new Date(Date.UTC(2012, 11, 20, 0, 0, 0)).toISOString() } }}
/>
<Node value={/hello/i} />
<Node value={new RegExp(`src(.*)\\.js$`)} />
{/* eslint-disable-next-line symbol-description */}
<Node value={Symbol()} />
<Node value={Symbol('Hello world')} />
<Node value={{ __date__: { value: new Date(1600000000000).toISOString() } }} />
<Node value={{ __date__: { value: new Date(1600000000123).toISOString() } }} />
<Node value={{ __error__: { name: 'EvalError', message: '' } }} />
<Node value={{ __error__: { name: 'SyntaxError', message: "Can't do that" } }} />
<Node
value={{
__error__: { name: 'TypeError', message: "Cannot read property 'foo' of undefined" },
}}
/>
<Node
value={{
__error__: { name: 'ReferenceError', message: 'Invalid left-hand side in assignment' },
}}
/>
<Node
value={{
__error__: {
name: 'Error',
message:
"XMLHttpRequest cannot load https://example.com. No 'Access-Control-Allow-Origin' header is present on the requested resource.",
},
}}
/>
<Node value={{ __regexp__: { flags: 'i', source: 'hello' } }} />
<Node value={{ __regexp__: { flags: '', source: 'src(.*)\\.js$' } }} />
<Node value={{ __symbol__: { description: '' } }} />
<Node value={{ __symbol__: { description: 'Hello world' } }} />
</div>
);
const calls: Call[] = [
{
cursor: 0,
id: '1',
path: ['screen'],
method: 'getByText',
@ -96,6 +107,7 @@ const calls: Call[] = [
retain: false,
},
{
cursor: 1,
id: '2',
path: ['userEvent'],
method: 'click',
@ -105,6 +117,7 @@ const calls: Call[] = [
retain: false,
},
{
cursor: 2,
id: '3',
path: [],
method: 'expect',
@ -114,6 +127,7 @@ const calls: Call[] = [
retain: false,
},
{
cursor: 3,
id: '4',
path: [{ __callId__: '3' }, 'not'],
method: 'toBe',
@ -123,15 +137,17 @@ const calls: Call[] = [
retain: false,
},
{
cursor: 4,
id: '5',
path: ['jest'],
method: 'fn',
storyId: 'kind--story',
args: [function actionHandler() {}],
args: [{ __function__: { name: 'actionHandler' } }],
interceptable: false,
retain: false,
},
{
cursor: 5,
id: '6',
path: [],
method: 'expect',
@ -141,20 +157,28 @@ const calls: Call[] = [
retain: false,
},
{
cursor: 6,
id: '7',
path: ['expect'],
method: 'stringMatching',
storyId: 'kind--story',
args: [/hello/i],
args: [{ __regexp__: { flags: 'i', source: 'hello' } }],
interceptable: false,
retain: false,
},
{
cursor: 7,
id: '8',
path: [{ __callId__: '6' }, 'not'],
method: 'toHaveBeenCalledWith',
storyId: 'kind--story',
args: [{ __callId__: '7' }, new Error("Cannot read property 'foo' of undefined")],
args: [
{ __callId__: '7' },
[
{ __error__: { name: 'Error', message: "Cannot read property 'foo' of undefined" } },
{ __symbol__: { description: 'Hello world' } },
],
],
interceptable: false,
retain: false,
},

View File

@ -111,32 +111,34 @@ export const Node = ({
return <NullNode {...props} />;
case value === undefined:
return <UndefinedNode {...props} />;
case Array.isArray(value):
return <ArrayNode {...props} value={value} callsById={callsById} />;
case typeof value === 'string':
return <StringNode value={value} {...props} />;
return <StringNode {...props} value={value} />;
case typeof value === 'number':
return <NumberNode value={value} {...props} />;
return <NumberNode {...props} value={value} />;
case typeof value === 'boolean':
return <BooleanNode value={value} {...props} />;
case typeof value === 'function':
return <FunctionNode value={value} {...props} />;
case value instanceof Array:
return <ArrayNode value={value} {...props} />;
case value instanceof Date:
return <DateNode value={value} {...props} />;
case value instanceof Error:
return <ErrorNode value={value} {...props} />;
case value instanceof RegExp:
return <RegExpNode value={value} {...props} />;
return <BooleanNode {...props} value={value} />;
/* eslint-disable no-underscore-dangle */
case Object.prototype.hasOwnProperty.call(value, '__date__'):
return <DateNode {...props} {...value.__date__} />;
case Object.prototype.hasOwnProperty.call(value, '__error__'):
return <ErrorNode {...props} {...value.__error__} />;
case Object.prototype.hasOwnProperty.call(value, '__regexp__'):
return <RegExpNode {...props} {...value.__regexp__} />;
case Object.prototype.hasOwnProperty.call(value, '__function__'):
return <FunctionNode {...props} {...value.__function__} />;
case Object.prototype.hasOwnProperty.call(value, '__symbol__'):
return <SymbolNode {...props} {...value.__symbol__} />;
case Object.prototype.hasOwnProperty.call(value, '__element__'):
// eslint-disable-next-line no-underscore-dangle
return <ElementNode value={value.__element__} {...props} />;
return <ElementNode {...props} {...value.__element__} />;
case Object.prototype.hasOwnProperty.call(value, '__class__'):
return <ClassNode {...props} {...value.__class__} />;
case Object.prototype.hasOwnProperty.call(value, '__callId__'):
// eslint-disable-next-line no-underscore-dangle
return <MethodCall call={callsById.get(value.__callId__)} callsById={callsById} />;
case typeof value === 'object' &&
value.constructor?.name &&
value.constructor?.name !== 'Object':
return <ClassNode value={value} {...props} />;
/* eslint-enable no-underscore-dangle */
case Object.prototype.toString.call(value) === '[object Object]':
return <ObjectNode value={value} showInspector={showObjectInspector} {...props} />;
default:
@ -189,12 +191,22 @@ export const BooleanNode = ({ value, ...props }: { value: boolean }) => {
);
};
export const ArrayNode = ({ value, nested = false }: { value: any[]; nested?: boolean }) => {
export const ArrayNode = ({
value,
nested = false,
callsById,
}: {
value: any[];
nested?: boolean;
callsById?: Map<Call['id'], Call>;
}) => {
const colors = useThemeColors();
if (nested) {
return <span style={{ color: colors.base }}>[]</span>;
}
const nodes = value.slice(0, 3).map((v) => <Node key={v} value={v} nested />);
const nodes = value
.slice(0, 3)
.map((v) => <Node key={JSON.stringify(v)} value={v} nested callsById={callsById} />);
const nodelist = interleave(nodes, <span>, </span>);
if (value.length <= 3) {
return <span style={{ color: colors.base }}>[{nodelist}]</span>;
@ -263,18 +275,27 @@ export const ObjectNode = ({
);
};
export const ClassNode = ({ value }: { value: Record<string, any> }) => {
export const ClassNode = ({ name }: { name: string }) => {
const colors = useThemeColors();
return <span style={{ color: colors.instance }}>{value.constructor.name}</span>;
return <span style={{ color: colors.instance }}>{name}</span>;
};
export const FunctionNode = ({ value }: { value: Function }) => {
export const FunctionNode = ({ name }: { name: string }) => {
const colors = useThemeColors();
return <span style={{ color: colors.function }}>{value.name || 'anonymous'}</span>;
return name ? (
<span style={{ color: colors.function }}>{name}</span>
) : (
<span style={{ color: colors.nullish, fontStyle: 'italic' }}>anonymous</span>
);
};
export const ElementNode = ({ value }: { value: ElementRef['__element__'] }) => {
const { prefix, localName, id, classNames = [], innerText } = value;
export const ElementNode = ({
prefix,
localName,
id,
classNames = [],
innerText,
}: ElementRef['__element__']) => {
const name = prefix ? `${prefix}:${localName}` : localName;
const colors = useThemeColors();
return (
@ -309,8 +330,8 @@ export const ElementNode = ({ value }: { value: ElementRef['__element__'] }) =>
);
};
export const DateNode = ({ value }: { value: Date }) => {
const [date, time, ms] = value.toISOString().split(/[T.Z]/);
export const DateNode = ({ value }: { value: string }) => {
const [date, time, ms] = value.split(/[T.Z]/);
const colors = useThemeColors();
return (
<span style={{ whiteSpace: 'nowrap', color: colors.date }}>
@ -323,42 +344,36 @@ export const DateNode = ({ value }: { value: Date }) => {
);
};
export const ErrorNode = ({ value }: { value: Error }) => {
export const ErrorNode = ({ name, message }: { name: string; message: string }) => {
const colors = useThemeColors();
return (
<span style={{ color: colors.error.name }}>
{value.name}
{value.message && ': '}
{value.message && (
<span
style={{ color: colors.error.message }}
title={value.message.length > 50 ? value.message : ''}
>
{ellipsize(value.message, 50)}
{name}
{message && ': '}
{message && (
<span style={{ color: colors.error.message }} title={message.length > 50 ? message : ''}>
{ellipsize(message, 50)}
</span>
)}
</span>
);
};
export const RegExpNode = ({ value }: { value: RegExp }) => {
export const RegExpNode = ({ flags, source }: { flags: string; source: string }) => {
const colors = useThemeColors();
return (
<span style={{ whiteSpace: 'nowrap', color: colors.regex.flags }}>
/<span style={{ color: colors.regex.source }}>{value.source}</span>/{value.flags}
/<span style={{ color: colors.regex.source }}>{source}</span>/{flags}
</span>
);
};
export const SymbolNode = ({ value }: { value: symbol }) => {
export const SymbolNode = ({ description }: { description: string }) => {
const colors = useThemeColors();
return (
<span style={{ whiteSpace: 'nowrap', color: colors.instance }}>
Symbol(
{value.description && (
<span style={{ color: colors.meta }}>{JSON.stringify(value.description)}</span>
)}
)
{description && <span style={{ color: colors.meta }}>"{description}"</span>})
</span>
);
};

View File

@ -12,6 +12,7 @@ export default {
goto: action('goto'),
next: action('next'),
end: action('end'),
rerun: action('rerun'),
},
controlStates: {
debugger: true,

View File

@ -1,31 +1,122 @@
import { CallStates, Call } from '@storybook/instrumenter';
export const getCall = (status: CallStates): Call => {
const defaultData = {
id: 'addons-interactions-accountform--standard-email-filled [3] change',
cursor: 0,
path: ['fireEvent'],
method: 'change',
storyId: 'addons-interactions-accountform--standard-email-filled',
args: [
{
__callId__: 'addons-interactions-accountform--standard-email-filled [2] getByTestId',
retain: false,
},
{
target: {
value: 'michael@chromatic.com',
},
},
],
interceptable: true,
retain: false,
status,
};
export const getCalls = (finalStatus: CallStates) => {
const calls: Call[] = [
{
id: 'story--id [3] within',
storyId: 'story--id',
cursor: 3,
path: [],
method: 'within',
args: [{ __element__: { localName: 'div', id: 'root' } }],
interceptable: false,
retain: false,
status: CallStates.DONE,
},
{
id: 'story--id [4] findByText',
storyId: 'story--id',
cursor: 4,
path: [{ __callId__: 'story--id [3] within' }],
method: 'findByText',
args: ['Click'],
interceptable: true,
retain: false,
status: CallStates.DONE,
},
{
id: 'story--id [5] click',
storyId: 'story--id',
cursor: 5,
path: ['userEvent'],
method: 'click',
args: [{ __element__: { localName: 'button', innerText: 'Click' } }],
interceptable: true,
retain: false,
status: CallStates.DONE,
},
{
id: 'story--id [6] waitFor',
storyId: 'story--id',
cursor: 6,
path: [],
method: 'waitFor',
args: [{ __function__: { name: '' } }],
interceptable: true,
retain: false,
status: CallStates.DONE,
},
{
id: 'story--id [6] waitFor [0] expect',
parentId: 'story--id [6] waitFor',
storyId: 'story--id',
cursor: 1,
path: [],
method: 'expect',
args: [{ __function__: { name: 'handleSubmit' } }],
interceptable: false,
retain: false,
status: CallStates.DONE,
},
{
id: 'story--id [6] waitFor [1] stringMatching',
parentId: 'story--id [6] waitFor',
storyId: 'story--id',
cursor: 2,
path: ['expect'],
method: 'stringMatching',
args: [{ __regexp__: { flags: 'gi', source: '([A-Z])w+' } }],
interceptable: false,
retain: false,
status: CallStates.DONE,
},
{
id: 'story--id [6] waitFor [2] toHaveBeenCalledWith',
parentId: 'story--id [6] waitFor',
storyId: 'story--id',
cursor: 3,
path: [{ __callId__: 'story--id [6] waitFor [0] expect' }],
method: 'toHaveBeenCalledWith',
args: [{ __callId__: 'story--id [6] waitFor [1] stringMatching', retain: false }],
interceptable: true,
retain: false,
status: CallStates.DONE,
},
{
id: 'story--id [7] expect',
storyId: 'story--id',
cursor: 7,
path: [],
method: 'expect',
args: [{ __function__: { name: 'handleReset' } }],
interceptable: false,
retain: false,
status: CallStates.DONE,
},
{
id: 'story--id [8] toHaveBeenCalled',
storyId: 'story--id',
cursor: 8,
path: [{ __callId__: 'story--id [7] expect' }, 'not'],
method: 'toHaveBeenCalled',
args: [],
interceptable: true,
retain: false,
status: finalStatus,
},
];
const overrides = CallStates.ERROR
? { exception: { name: 'Error', stack: '', message: "Things didn't work!" } }
: {};
if (finalStatus === CallStates.ERROR) {
calls[calls.length - 1].exception = {
name: 'Error',
stack: '',
message: 'Oops!',
callId: calls[calls.length - 1].id,
};
}
return { ...defaultData, ...overrides };
return calls;
};
export const getInteractions = (finalStatus: CallStates) =>
getCalls(finalStatus).filter((call) => call.interceptable);

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-jest",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "React storybook addon that show component jest report",
"keywords": [
"addon",
@ -47,12 +47,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/components": "6.5.0-beta.1",
"@storybook/core-events": "6.5.0-beta.1",
"@storybook/theming": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/components": "6.5.0-rc.1",
"@storybook/core-events": "6.5.0-rc.1",
"@storybook/theming": "6.5.0-rc.1",
"core-js": "^3.8.2",
"global": "^4.4.0",
"react-sizeme": "^3.0.1",
@ -77,7 +77,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/index.js",
"storybook": {
"displayName": "Jest",

View File

@ -99,13 +99,13 @@ const getColorByType = (type: string) => {
// using switch to allow for new types to be added
switch (type) {
case StatusTypes.PASSED_TYPE:
return convert(themes.normal).color.positive;
return convert(themes.light).color.positive;
case StatusTypes.FAILED_TYPE:
return convert(themes.normal).color.negative;
return convert(themes.light).color.negative;
case StatusTypes.PENDING_TYPE:
return convert(themes.normal).color.warning;
return convert(themes.light).color.warning;
case StatusTypes.TODO_TYPE:
return convert(themes.normal).color.purple;
return convert(themes.light).color.purple;
default:
return null;
}
@ -154,7 +154,7 @@ const Content = styled(({ tests, className }: ContentProps) => (
</SuiteHead>
<TabsState
initial="failing-tests"
backgroundColor={convert(themes.normal).background.hoverable}
backgroundColor={convert(themes.light).background.hoverable}
>
<div
id="failing-tests"

View File

@ -67,7 +67,7 @@ export function Result(props: ResultProps) {
<Icon
icon="chevrondown"
size={10}
color={convert(themes.normal).color.mediumdark}
color={convert(themes.light).color.mediumdark}
style={{
transform: `rotate(${isOpen ? 0 : -90}deg)`,
}}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-links",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Link stories together to build demos and prototypes with your UI components",
"keywords": [
"addon",
@ -41,11 +41,11 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/core-events": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/core-events": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/router": "6.5.0-beta.1",
"@storybook/router": "6.5.0-rc.1",
"@types/qs": "^6.9.5",
"core-js": "^3.8.2",
"global": "^4.4.0",
@ -72,7 +72,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/index.js",
"storybook": {
"displayName": "Links",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-measure",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Inspect layouts by visualizing the box model",
"keywords": [
"storybook-addons",
@ -44,11 +44,11 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/components": "6.5.0-beta.1",
"@storybook/core-events": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/components": "6.5.0-rc.1",
"@storybook/core-events": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"core-js": "^3.8.2",
"global": "^4.4.0"
@ -71,7 +71,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/index.js",
"storybook": {
"displayName": "Measure",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-outline",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Outline all elements with CSS to help with layout placement and alignment",
"keywords": [
"storybook-addons",
@ -47,11 +47,11 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/components": "6.5.0-beta.1",
"@storybook/core-events": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/components": "6.5.0-rc.1",
"@storybook/core-events": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"core-js": "^3.8.2",
"global": "^4.4.0",
@ -76,7 +76,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/index.js",
"storybook": {
"displayName": "Outline",

View File

@ -470,7 +470,7 @@ Whenever you change your data requirements by adding (and rendering) or (acciden
## Using a custom directory
Depending on your project's needs, you can configure the `@storybook/addon-storyshots` to use a custom directory for the snapshots. You can read more about it in the [official docs](https://storybook.js.org/docs/react/workflows/snapshot-testing).
Depending on your project's needs, you can configure the `@storybook/addon-storyshots` to use a custom directory for the snapshots. You can read more about it in the [official docs](https://storybook.js.org/docs/react/writing-tests/snapshot-testing).
## Options
@ -654,7 +654,7 @@ This option needs to be set if either:
### `serializer` (deprecated)
Pass a custom serializer (such as enzyme-to-json) to serialize components to snapshot-comparable data. The functionality of this option is completely covered by [snapshotSerializers](`snapshotSerializers`) which should be used instead.
Pass a custom serializer (such as enzyme-to-json) to serialize components to snapshot-comparable data. The functionality of this option is completely covered by [snapshotSerializers](#snapshotserializers) which should be used instead.
```js
import initStoryshots from '@storybook/addon-storyshots';

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storyshots",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Take a code snapshot of every story automatically with Jest",
"keywords": [
"addon",
@ -45,12 +45,12 @@
},
"dependencies": {
"@jest/transform": "^26.6.2",
"@storybook/addons": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/babel-plugin-require-context-hook": "1.0.1",
"@storybook/client-api": "6.5.0-beta.1",
"@storybook/core": "6.5.0-beta.1",
"@storybook/core-client": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/client-api": "6.5.0-rc.1",
"@storybook/core": "6.5.0-rc.1",
"@storybook/core-client": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@types/glob": "^7.1.3",
"@types/jest": "^26.0.16",
@ -70,11 +70,11 @@
"@angular/core": "^11.2.0",
"@angular/platform-browser-dynamic": "^11.2.0",
"@emotion/jest": "^11.8.0",
"@storybook/addon-docs": "6.5.0-beta.1",
"@storybook/angular": "6.5.0-beta.1",
"@storybook/react": "6.5.0-beta.1",
"@storybook/vue": "6.5.0-beta.1",
"@storybook/vue3": "6.5.0-beta.1",
"@storybook/addon-docs": "6.5.0-rc.1",
"@storybook/angular": "6.5.0-rc.1",
"@storybook/react": "6.5.0-rc.1",
"@storybook/vue": "6.5.0-rc.1",
"@storybook/vue3": "6.5.0-rc.1",
"babel-loader": "^8.0.0",
"enzyme": "^3.11.0",
"enzyme-to-json": "^3.6.1",
@ -151,7 +151,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"storybook": {
"displayName": "Storyshots",
"icon": "https://user-images.githubusercontent.com/263385/101991676-48cdf300-3c7c-11eb-8aa1-944dab6ab29b.png",

View File

@ -1,15 +1,21 @@
import 'jest-specific-snapshot';
import { StoryshotsTestMethod, TestMethodOptions } from './api/StoryshotsOptions';
import {
StoryshotsTestMethod,
TestMethodOptions,
StoryshotsOptions,
} from './api/StoryshotsOptions';
const isFunction = (obj: any) => !!(obj && obj.constructor && obj.call && obj.apply);
const optionsOrCallOptions = (opts: any, story: any) => (isFunction(opts) ? opts(story) : opts);
type SnapshotsWithOptionsArgType = Pick<StoryshotsOptions, 'renderer' | 'serializer'> | Function;
type SnapshotsWithOptionsReturnType = (
options: Pick<TestMethodOptions, 'story' | 'context' | 'renderTree' | 'snapshotFileName'>
) => any;
export function snapshotWithOptions(
options: { renderer?: any; serializer?: any } | Function = {}
options: SnapshotsWithOptionsArgType = {}
): SnapshotsWithOptionsReturnType {
return ({ story, context, renderTree, snapshotFileName }) => {
const result = renderTree(story, context, optionsOrCallOptions(options, story));
@ -44,7 +50,9 @@ export function snapshotWithOptions(
};
}
export function multiSnapshotWithOptions(options = {}): StoryshotsTestMethod {
export function multiSnapshotWithOptions(
options: SnapshotsWithOptionsArgType = {}
): StoryshotsTestMethod {
return ({ story, context, renderTree, stories2snapsConverter }) => {
const snapshotFileName = stories2snapsConverter.getSnapshotFileName(context);
return snapshotWithOptions(options)({ story, context, renderTree, snapshotFileName });

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storyshots-puppeteer",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Image snapshots addition to StoryShots based on puppeteer",
"keywords": [
"addon",
@ -42,7 +42,7 @@
"dependencies": {
"@axe-core/puppeteer": "^4.2.0",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/node-logger": "6.5.0-beta.1",
"@storybook/node-logger": "6.5.0-rc.1",
"@types/jest-image-snapshot": "^4.1.3",
"core-js": "^3.8.2",
"jest-image-snapshot": "^4.3.0",
@ -50,11 +50,12 @@
},
"devDependencies": {
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@types/puppeteer": "^5.4.0"
"@types/puppeteer": "^5.4.0",
"puppeteer": "^2.0.0 || ^3.0.0"
},
"peerDependencies": {
"@storybook/addon-storyshots": "6.5.0-beta.1",
"puppeteer": "^2.0.0 || ^3.0.0"
"@storybook/addon-storyshots": "6.5.0-rc.1",
"puppeteer": ">=2.0.0"
},
"peerDependenciesMeta": {
"puppeteer": {
@ -64,5 +65,5 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a"
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401"
}

View File

@ -1,11 +1,7 @@
import { MatchImageSnapshotOptions } from 'jest-image-snapshot';
import {
Base64ScreenShotOptions,
Browser,
DirectNavigationOptions,
Page,
ElementHandle,
} from 'puppeteer';
import { ScreenshotOptions, Browser, Page, ElementHandle } from 'puppeteer';
type PuppeteerLifeCycleEvent = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2';
export interface Context {
kind: string;
@ -20,6 +16,16 @@ interface Options {
url: string;
}
interface Base64ScreenShotOptions extends ScreenshotOptions {
encoding: 'base64';
}
interface DirectNavigationOptions {
referer?: string;
timeout?: number;
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
}
export interface CommonConfig {
storybookUrl: string;
chromeExecutablePath: string;
@ -40,7 +46,7 @@ export interface ImageSnapshotConfig extends CommonConfig {
getMatchOptions: (options: Options) => MatchImageSnapshotOptions;
getScreenshotOptions: (options: Options) => Base64ScreenShotOptions;
beforeScreenshot: (page: Page, options: Options) => Promise<void | ElementHandle>;
afterScreenshot: (options: { image: string; context: Context }) => Promise<void>;
afterScreenshot: (options: { image: string | void | Buffer; context: Context }) => Promise<void>;
}
export interface AxeConfig extends CommonConfig {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storysource",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "View a storys source code to see how it works and paste into your app",
"keywords": [
"addon",
@ -41,18 +41,18 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/components": "6.5.0-beta.1",
"@storybook/router": "6.5.0-beta.1",
"@storybook/source-loader": "6.5.0-beta.1",
"@storybook/theming": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/components": "6.5.0-rc.1",
"@storybook/router": "6.5.0-rc.1",
"@storybook/source-loader": "6.5.0-rc.1",
"@storybook/theming": "6.5.0-rc.1",
"core-js": "^3.8.2",
"estraverse": "^5.2.0",
"loader-utils": "^2.0.0",
"prop-types": "^15.7.2",
"react-syntax-highlighter": "^15.4.5",
"react-syntax-highlighter": "^15.5.0",
"regenerator-runtime": "^0.13.7"
},
"devDependencies": {
@ -74,7 +74,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/index.js",
"storybook": {
"displayName": "Storysource",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-toolbars",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Create your own toolbar items that control story rendering",
"keywords": [
"addon",
@ -45,11 +45,11 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/components": "6.5.0-beta.1",
"@storybook/theming": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/components": "6.5.0-rc.1",
"@storybook/theming": "6.5.0-rc.1",
"core-js": "^3.8.2",
"regenerator-runtime": "^0.13.7"
},
@ -68,7 +68,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/manager.js",
"storybook": {
"displayName": "Toolbars",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-viewport",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Build responsive components by adjusting Storybooks viewport size and orientation",
"keywords": [
"addon",
@ -42,12 +42,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/components": "6.5.0-beta.1",
"@storybook/core-events": "6.5.0-beta.1",
"@storybook/theming": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/components": "6.5.0-rc.1",
"@storybook/core-events": "6.5.0-rc.1",
"@storybook/theming": "6.5.0-rc.1",
"core-js": "^3.8.2",
"global": "^4.4.0",
"memoizerific": "^1.11.3",
@ -69,7 +69,7 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/preview.js",
"storybook": {
"displayName": "Viewport",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/angular",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Storybook for Angular: Develop Angular Components in isolation with Hot Reloading.",
"keywords": [
"storybook"
@ -45,17 +45,17 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/core": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/core-events": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/core": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/core-events": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/docs-tools": "6.5.0-beta.1",
"@storybook/node-logger": "6.5.0-beta.1",
"@storybook/docs-tools": "6.5.0-rc.1",
"@storybook/node-logger": "6.5.0-rc.1",
"@storybook/semver": "^7.3.2",
"@storybook/store": "6.5.0-beta.1",
"@storybook/store": "6.5.0-rc.1",
"@types/node": "^14.14.20 || ^16.0.0",
"@types/react": "^16.14.23",
"@types/react-dom": "^16.9.14",
@ -75,7 +75,7 @@
"read-pkg-up": "^7.0.1",
"regenerator-runtime": "^0.13.7",
"sass-loader": "^10.1.0",
"telejson": "^5.3.3",
"telejson": "^6.0.8",
"ts-dedent": "^2.0.0",
"ts-loader": "^8.0.14",
"tsconfig-paths-webpack-plugin": "^3.3.0",
@ -137,5 +137,5 @@
"access": "public"
},
"builders": "dist/ts3.9/builders/builders.json",
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a"
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401"
}

View File

@ -1,4 +1,4 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import 'jest-preset-angular';
import 'jest-preset-angular/setup-jest';
global.EventSource = class {} as any;

View File

@ -142,7 +142,7 @@ describe('Build Storybook Builder', () => {
});
it('should throw error', async () => {
buildStandaloneMock.mockRejectedValue(new Error());
buildStandaloneMock.mockRejectedValue(true);
const run = await architect.scheduleBuilder('@storybook/angular:start-storybook', {
browserTarget: 'angular-cli:build-2',

View File

@ -124,7 +124,7 @@ describe('Start Storybook Builder', () => {
});
it('should throw error', async () => {
buildStandaloneMock.mockRejectedValue(new Error());
buildStandaloneMock.mockRejectedValue(true);
const run = await architect.scheduleBuilder('@storybook/angular:start-storybook', {
browserTarget: 'angular-cli:build-2',

View File

@ -21,11 +21,11 @@ export const buildStandaloneErrorHandler = (error: any): any => {
logger.line();
return error.close
? dedent`
FATAL broken build!, will close the process,
Fix the error below and restart storybook.
`
FATAL broken build!, will close the process,
Fix the error below and restart storybook.
`
: dedent`
Broken build, fix the error above.
You may need to refresh the browser.
`;
Broken build, fix the error above.
You may need to refresh the browser.
`;
};

View File

@ -22,6 +22,7 @@ describe('RendererFactory', () => {
rootTargetDOMNode = global.document.getElementById('root');
rootDocstargetDOMNode = global.document.getElementById('root-docs');
(platformBrowserDynamic as any).mockImplementation(platformBrowserDynamicTesting);
jest.spyOn(console, 'log').mockImplementation(() => {});
});
afterEach(() => {

View File

@ -7,7 +7,7 @@ import deprecate from 'util-deprecate';
import { ICollection, StoryFnAngularReturnType } from '../types';
import { storyPropsProvider } from './StorybookProvider';
import { isComponentAlreadyDeclaredInModules } from './utils/NgModulesAnalyzer';
import { isDeclarable } from './utils/NgComponentAnalyzer';
import { isDeclarable, isStandaloneComponent } from './utils/NgComponentAnalyzer';
import { createStorybookWrapperComponent } from './StorybookWrapperComponent';
import { computesTemplateFromComponent } from './ComputesTemplateFromComponent';
@ -61,6 +61,7 @@ export const getStorybookModuleMetadata = (
props
);
const isStandalone = isStandaloneComponent(component);
// Look recursively (deep) if the component is not already declared by an import module
const requiresComponentDeclaration =
isDeclarable(component) &&
@ -68,7 +69,8 @@ export const getStorybookModuleMetadata = (
component,
moduleMetadata.declarations,
moduleMetadata.imports
);
) &&
!isStandalone;
return {
declarations: [
@ -76,7 +78,11 @@ export const getStorybookModuleMetadata = (
ComponentToInject,
...(moduleMetadata.declarations ?? []),
],
imports: [BrowserModule, ...(moduleMetadata.imports ?? [])],
imports: [
BrowserModule,
...(isStandalone ? [component] : []),
...(moduleMetadata.imports ?? []),
],
providers: [storyPropsProvider(storyProps$), ...(moduleMetadata.providers ?? [])],
entryComponents: [...(moduleMetadata.entryComponents ?? [])],
schemas: [...(moduleMetadata.schemas ?? [])],

View File

@ -19,6 +19,7 @@ import {
isComponent,
isDeclarable,
getComponentDecoratorMetadata,
isStandaloneComponent,
} from './NgComponentAnalyzer';
describe('getComponentInputsOutputs', () => {
@ -235,6 +236,46 @@ describe('isComponent', () => {
});
});
describe('isStandaloneComponent', () => {
it('should return true with a Component with "standalone: true"', () => {
// TODO: `standalone` is only available in Angular v14. Remove cast to `any` once
// Angular deps are updated to v14.x.x.
@Component({ standalone: true } as any)
class FooComponent {}
expect(isStandaloneComponent(FooComponent)).toEqual(true);
});
it('should return false with a Component with "standalone: false"', () => {
// TODO: `standalone` is only available in Angular v14. Remove cast to `any` once
// Angular deps are updated to v14.x.x.
@Component({ standalone: false } as any)
class FooComponent {}
expect(isStandaloneComponent(FooComponent)).toEqual(false);
});
it('should return false with a Component without the "standalone" property', () => {
@Component({})
class FooComponent {}
expect(isStandaloneComponent(FooComponent)).toEqual(false);
});
it('should return false with simple class', () => {
class FooPipe {}
expect(isStandaloneComponent(FooPipe)).toEqual(false);
});
it('should return false with Directive', () => {
@Directive()
class FooDirective {}
expect(isStandaloneComponent(FooDirective)).toEqual(false);
});
});
describe('getComponentDecoratorMetadata', () => {
it('should return Component with a Component', () => {
@Component({ selector: 'foo' })

View File

@ -108,6 +108,18 @@ export const isComponent = (component: any): component is Type<unknown> => {
return (decorators || []).some((d) => d instanceof Component);
};
export const isStandaloneComponent = (component: any): component is Type<unknown> => {
if (!component) {
return false;
}
const decorators = reflectionCapabilities.annotations(component);
// TODO: `standalone` is only available in Angular v14. Remove cast to `any` once
// Angular deps are updated to v14.x.x.
return (decorators || []).some((d) => d instanceof Component && (d as any).standalone);
};
/**
* Returns all component decorator properties
* is used to get all `@Input` and `@Output` Decorator

View File

@ -2,6 +2,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "../../out-tsc/lib",
"target": "es2015",
"declaration": true,

View File

@ -2,6 +2,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "../../out-tsc/lib",
"target": "es2015",
"declaration": true,

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/ember",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.",
"homepage": "https://github.com/storybookjs/storybook/tree/main/app/ember",
"bugs": {
@ -42,10 +42,10 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/core": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/docs-tools": "6.5.0-beta.1",
"@storybook/store": "6.5.0-beta.1",
"@storybook/core": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/docs-tools": "6.5.0-rc.1",
"@storybook/store": "6.5.0-rc.1",
"core-js": "^3.8.2",
"global": "^4.4.0",
"react": "16.14.0",
@ -56,9 +56,10 @@
},
"peerDependencies": {
"@babel/core": "*",
"@types/ember__component": "4.0.8",
"babel-plugin-ember-modules-api-polyfill": "^2.12.0",
"babel-plugin-htmlbars-inline-precompile": "2 || 3",
"ember-source": "^3.16.0"
"ember-source": "~3.28.1"
},
"engines": {
"node": ">=10.13.0"
@ -66,6 +67,6 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/client/index.js"
}

View File

@ -1,12 +1,12 @@
import global from 'global';
import dedent from 'ts-dedent';
import { RenderContext } from '@storybook/store';
import type { RenderContext } from '@storybook/store';
// @ts-ignore
import Component from '@ember/component'; // eslint-disable-line import/no-unresolved
import { OptionsArgs, EmberFramework } from './types';
const { window: globalWindow, document } = global;
declare let Ember: any;
const rootEl = document.getElementById('root');
const config = globalWindow.require(`${globalWindow.STORYBOOK_NAME}/config/environment`);
@ -38,7 +38,7 @@ function render(options: OptionsArgs, el: HTMLElement) {
.then((instance: any) => {
instance.register(
'component:story-mode',
Ember.Component.extend({
Component.extend({
layout: template || options,
...context,
})

View File

@ -19,6 +19,11 @@ npx sb init -t html
For more information visit: [storybook.js.org](https://storybook.js.org)
### Typescript
`npx sb init` will select `.ts` starter stories if your `package.json` has typescript as a dependency. If starting a new project,
run `npm init` and `npm install typescript --save-dev` before initializing storybook to get the typescript starter stories.
---
Storybook also comes with a lot of [addons](https://storybook.js.org/addons) and a great API to customize as you wish.

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/html",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.",
"keywords": [
"storybook"
@ -45,13 +45,13 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/core": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/core": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/docs-tools": "6.5.0-beta.1",
"@storybook/preview-web": "6.5.0-beta.1",
"@storybook/store": "6.5.0-beta.1",
"@storybook/docs-tools": "6.5.0-rc.1",
"@storybook/preview-web": "6.5.0-rc.1",
"@storybook/store": "6.5.0-rc.1",
"@types/node": "^14.14.20 || ^16.0.0",
"@types/webpack-env": "^1.16.0",
"core-js": "^3.8.2",
@ -76,6 +76,6 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/client/index.js"
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/preact",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Storybook for Preact: Develop Preact Component in isolation.",
"keywords": [
"storybook"
@ -46,11 +46,11 @@
},
"dependencies": {
"@babel/plugin-transform-react-jsx": "^7.12.12",
"@storybook/addons": "6.5.0-beta.1",
"@storybook/core": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/core": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/store": "6.5.0-beta.1",
"@storybook/store": "6.5.0-rc.1",
"@types/node": "^14.14.20 || ^16.0.0",
"@types/webpack-env": "^1.16.0",
"core-js": "^3.8.2",
@ -76,6 +76,6 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/client/index.js"
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/react",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Storybook for React: Develop React Component in isolation with Hot Reloading.",
"keywords": [
"storybook"
@ -49,16 +49,16 @@
"@babel/preset-flow": "^7.12.1",
"@babel/preset-react": "^7.12.10",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
"@storybook/addons": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/core": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/core": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/docs-tools": "6.5.0-beta.1",
"@storybook/node-logger": "6.5.0-beta.1",
"@storybook/docs-tools": "6.5.0-rc.1",
"@storybook/node-logger": "6.5.0-rc.1",
"@storybook/react-docgen-typescript-plugin": "1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0",
"@storybook/semver": "^7.3.2",
"@storybook/store": "6.5.0-beta.1",
"@storybook/store": "6.5.0-rc.1",
"@types/estree": "^0.0.51",
"@types/node": "^14.14.20 || ^16.0.0",
"@types/webpack-env": "^1.16.0",
@ -119,6 +119,6 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/client/index.js"
}

View File

@ -284,4 +284,33 @@ describe('jsxDecorator', () => {
'<div className="foo" />'
);
});
it('handles stories that trigger Suspense', async () => {
// if a story function uses a hook or other library that triggers suspense, it will throw a Promise until it is resolved
// and then it will return the story content after the promise is resolved
const storyFn = jest.fn();
storyFn
.mockImplementationOnce(() => {
throw Promise.resolve();
})
.mockImplementation(() => {
return <div>resolved args story</div>;
});
const jsx = '';
const context = makeContext('args', { __isArgsStory: true, jsx }, {});
expect(() => {
jsxDecorator(storyFn, context);
}).toThrow(Promise);
jsxDecorator(storyFn, context);
await new Promise((r) => setTimeout(r, 0));
expect(mockChannel.emit).toHaveBeenCalledTimes(2);
expect(mockChannel.emit).nthCalledWith(1, SNIPPET_RENDERED, 'jsx-test--args', '');
expect(mockChannel.emit).nthCalledWith(
2,
SNIPPET_RENDERED,
'jsx-test--args',
'<div>\n resolved args story\n</div>'
);
});
});

View File

@ -177,7 +177,6 @@ export const jsxDecorator = (
) => {
const channel = addons.getChannel();
const skip = skipJsxRender(context);
const story = storyFn();
let jsx = '';
@ -185,6 +184,7 @@ export const jsxDecorator = (
if (!skip) channel.emit(SNIPPET_RENDERED, (context || {}).id, jsx);
});
const story = storyFn();
// We only need to render JSX if the source block is actually going to
// consume it. Otherwise it's just slowing us down.
if (skip) {

View File

@ -148,7 +148,7 @@ describe('parse', () => {
});
it('support array', () => {
const result = parse("['bottom-left', 'botton-center', 'bottom-right']");
const result = parse("['bottom-left', 'bottom-center', 'bottom-right']");
const inferredType = result.inferredType as InspectionArray;
expect(inferredType.type).toBe(InspectionType.ARRAY);

View File

@ -48,7 +48,7 @@ const renderElement = async (node: ReactElement, el: Element) => {
};
const canUseNewReactRootApi =
reactDomVersion.startsWith('18') || reactDomVersion.startsWith('0.0.0');
reactDomVersion && (reactDomVersion.startsWith('18') || reactDomVersion.startsWith('0.0.0'));
const shouldUseNewRootApi = FRAMEWORK_OPTIONS?.legacyRootApi !== true;
@ -147,4 +147,6 @@ export async function renderToDOM(
}
await renderElement(element, domElement);
return () => unmountElement(domElement);
}

View File

@ -32,7 +32,7 @@ describe('framework-preset-react-docgen', () => {
presets: ['env', 'foo-preset'],
overrides: [
{
test: /\.(mjs|tsx?|jsx?)$/,
test: /\.(cjs|mjs|tsx?|jsx?)$/,
plugins: [
[
babelPluginReactDocgenPath,

View File

@ -19,8 +19,9 @@ export async function babel(config: TransformOptions, options: Options) {
return {
...config,
overrides: [
...(config?.overrides || []),
{
test: reactDocgen === 'react-docgen' ? /\.(mjs|tsx?|jsx?)$/ : /\.(mjs|jsx?)$/,
test: reactDocgen === 'react-docgen' ? /\.(cjs|mjs|tsx?|jsx?)$/ : /\.(cjs|mjs|jsx?)$/,
plugins: [
[
require.resolve('babel-plugin-react-docgen'),

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/server",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.",
"keywords": [
"storybook"
@ -45,15 +45,15 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/api": "6.5.0-beta.1",
"@storybook/client-api": "6.5.0-beta.1",
"@storybook/core": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/api": "6.5.0-rc.1",
"@storybook/client-api": "6.5.0-rc.1",
"@storybook/core": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/node-logger": "6.5.0-beta.1",
"@storybook/preview-web": "6.5.0-beta.1",
"@storybook/store": "6.5.0-beta.1",
"@storybook/node-logger": "6.5.0-rc.1",
"@storybook/preview-web": "6.5.0-rc.1",
"@storybook/store": "6.5.0-rc.1",
"@types/node": "^14.14.20 || ^16.0.0",
"@types/webpack-env": "^1.16.0",
"core-js": "^3.8.2",
@ -76,6 +76,6 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/client/index.js"
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/svelte",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.",
"keywords": [
"storybook"
@ -46,14 +46,14 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/core": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/core": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/docs-tools": "6.5.0-beta.1",
"@storybook/node-logger": "6.5.0-beta.1",
"@storybook/store": "6.5.0-beta.1",
"@storybook/docs-tools": "6.5.0-rc.1",
"@storybook/node-logger": "6.5.0-rc.1",
"@storybook/store": "6.5.0-rc.1",
"core-js": "^3.8.2",
"global": "^4.4.0",
"loader-utils": "^2.0.0",
@ -83,6 +83,6 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/client/index.js"
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/vue",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Storybook for Vue: Develop Vue Component in isolation with Hot Reloading.",
"keywords": [
"storybook"
@ -45,13 +45,13 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/core": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/core": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/docs-tools": "6.5.0-beta.1",
"@storybook/store": "6.5.0-beta.1",
"@storybook/docs-tools": "6.5.0-rc.1",
"@storybook/store": "6.5.0-rc.1",
"@types/node": "^14.14.20 || ^16.0.0",
"@types/webpack-env": "^1.16.0",
"core-js": "^3.8.2",
@ -86,6 +86,6 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/client/index.js"
}

View File

@ -1,4 +1,4 @@
export { renderToDOM } from './render';
export { decorateStory } from './decorateStory';
export { render, renderToDOM } from './render';
export { decorateStory as applyDecorators } from './decorateStory';
export const parameters = { framework: 'vue' };

View File

@ -22,7 +22,7 @@ const root = new Vue({
});
export const render: ArgsStoryFn<VueFramework> = (props, context) => {
const { id, component: Component } = context;
const { id, component: Component, argTypes } = context;
const component = Component as VueFramework['component'] & {
__docgenInfo?: { displayName: string };
props: Record<string, any>;
@ -49,7 +49,7 @@ export const render: ArgsStoryFn<VueFramework> = (props, context) => {
}
return {
props: component.props,
props: Object.keys(argTypes),
components: { [componentName]: component },
template: `<${componentName} v-bind="$props" />`,
};

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/vue3",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Storybook for Vue 3: Develop Vue 3 Components in isolation with Hot Reloading.",
"keywords": [
"storybook"
@ -45,12 +45,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "6.5.0-beta.1",
"@storybook/core": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/core": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/docs-tools": "6.5.0-beta.1",
"@storybook/store": "6.5.0-beta.1",
"@storybook/docs-tools": "6.5.0-rc.1",
"@storybook/store": "6.5.0-rc.1",
"@types/node": "^14.14.20 || ^16.0.0",
"@types/webpack-env": "^1.16.0",
"core-js": "^3.8.2",
@ -83,6 +83,6 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/client/index.js"
}

View File

@ -2,7 +2,7 @@ import type { StrictArgTypes } from '@storybook/csf';
import type { ArgTypesExtractor } from '@storybook/docs-tools';
import { hasDocgen, extractComponentProps, convert } from '@storybook/docs-tools';
const SECTIONS = ['props', 'events', 'slots'];
const SECTIONS = ['props', 'events', 'slots', 'methods'];
export const extractArgTypes: ArgTypesExtractor = (component) => {
if (!hasDocgen(component)) {

View File

@ -1,4 +1,4 @@
export { renderToDOM } from './render';
export { decorateStory } from './decorateStory';
export { render, renderToDOM } from './render';
export { decorateStory as applyDecorators } from './decorateStory';
export const parameters = { framework: 'vue3' };

View File

@ -24,7 +24,7 @@ export function webpack(config: Configuration): Configuration {
options: {},
},
{
test: /\.tsx?$/,
test: /\.ts$/,
use: [
{
loader: require.resolve('ts-loader'),
@ -35,6 +35,19 @@ export function webpack(config: Configuration): Configuration {
},
],
},
{
test: /\.tsx$/,
use: [
{
loader: require.resolve('ts-loader'),
options: {
transpileOnly: true,
// Note this is different from the `appendTsSuffixTo` above!
appendTsxSuffixTo: [/\.vue$/],
},
},
],
},
],
},
resolve: {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/web-components",
"version": "6.5.0-beta.1",
"version": "6.5.0-rc.1",
"description": "Storybook for web-components: View web components snippets in isolation with Hot Reloading.",
"keywords": [
"lit-html",
@ -50,15 +50,15 @@
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/preset-env": "^7.12.11",
"@storybook/addons": "6.5.0-beta.1",
"@storybook/client-api": "6.5.0-beta.1",
"@storybook/client-logger": "6.5.0-beta.1",
"@storybook/core": "6.5.0-beta.1",
"@storybook/core-common": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-rc.1",
"@storybook/client-api": "6.5.0-rc.1",
"@storybook/client-logger": "6.5.0-rc.1",
"@storybook/core": "6.5.0-rc.1",
"@storybook/core-common": "6.5.0-rc.1",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
"@storybook/docs-tools": "6.5.0-beta.1",
"@storybook/preview-web": "6.5.0-beta.1",
"@storybook/store": "6.5.0-beta.1",
"@storybook/docs-tools": "6.5.0-rc.1",
"@storybook/preview-web": "6.5.0-rc.1",
"@storybook/store": "6.5.0-rc.1",
"@types/node": "^14.14.20 || ^16.0.0",
"@types/webpack-env": "^1.16.0",
"babel-plugin-bundled-import-meta": "^0.3.1",
@ -82,6 +82,6 @@
"publishConfig": {
"access": "public"
},
"gitHead": "85bcae3041a0664d7c0ee4756241e29ad1063a9a",
"gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401",
"sbmodern": "dist/modern/client/index.js"
}

View File

@ -1,13 +1,13 @@
import { html } from 'lit-html';
import { styleMap } from 'lit-html/directives/style-map';
import { addons, useEffect } from '@storybook/addons';
import type { StoryContext } from '@storybook/addons';
import { SNIPPET_RENDERED } from '@storybook/docs-tools';
import type { StoryContext, WebComponentsFramework } from '..';
import { sourceDecorator } from './sourceDecorator';
jest.mock('@storybook/addons');
const mockedAddons = addons as jest.Mocked<typeof addons>;
const mockedUseEffect = useEffect as jest.Mocked<typeof useEffect>;
const mockedUseEffect = useEffect as jest.Mock;
expect.addSnapshotSerializer({
print: (val: any) => val,
@ -16,16 +16,22 @@ expect.addSnapshotSerializer({
const tick = () => new Promise((r) => setTimeout(r, 0));
const makeContext = (name: string, parameters: any, args: any, extra?: object): StoryContext => ({
id: `lit-test--${name}`,
kind: 'js-text',
name,
parameters,
args,
argTypes: {},
globals: {},
...extra,
});
const makeContext = (
name: string,
parameters: any,
args: any,
extra?: Partial<StoryContext<WebComponentsFramework>>
) =>
({
id: `lit-test--${name}`,
kind: 'js-text',
name,
parameters,
args,
argTypes: {},
globals: {},
...extra,
} as StoryContext<WebComponentsFramework>);
describe('sourceDecorator', () => {
let mockChannel: { on: jest.Mock; emit?: jest.Mock };
@ -106,4 +112,23 @@ describe('sourceDecorator', () => {
sourceDecorator(storyFn, context);
expect(transformSource).toHaveBeenCalledWith('<div>args story</div>', context);
});
it('should clean lit expression comments', async () => {
const storyFn = (args: any) => html`<div>${args.slot}</div>`;
const context = makeContext(
'args',
{ __isArgsStory: true },
{ slot: 'some content' },
{ originalStoryFn: storyFn }
);
// bind args to storyFn, as it's done in Storybook
const boundStoryFn = storyFn.bind(null, context.args);
sourceDecorator(boundStoryFn, context);
await tick();
expect(mockChannel.emit).toHaveBeenCalledWith(
SNIPPET_RENDERED,
'lit-test--args',
'<div>some content</div>'
);
});
});

View File

@ -6,6 +6,9 @@ import { SNIPPET_RENDERED, SourceType } from '@storybook/docs-tools';
import type { WebComponentsFramework } from '..';
// Taken from https://github.com/lit/lit/blob/main/packages/lit-html/src/test/test-utils/strip-markers.ts
const LIT_EXPRESSION_COMMENTS = /<!--\?lit\$[0-9]+\$-->|<!--\??-->/g;
function skipSourceRender(context: StoryContext<WebComponentsFramework>) {
const sourceParams = context?.parameters.docs?.source;
const isArgsStory = context?.parameters.__isArgsStory;
@ -44,7 +47,10 @@ export function sourceDecorator(
if (!skipSourceRender(context)) {
const container = window.document.createElement('div');
render(story, container);
source = applyTransformSource(container.innerHTML.replace(/<!---->/g, ''), context);
source = applyTransformSource(
container.innerHTML.replace(LIT_EXPRESSION_COMMENTS, ''),
context
);
}
return story;

View File

@ -362,7 +362,7 @@ The following table details how to use the API values:
| **showPanel** | Boolean | Display panel that shows addon configurations | `true` |
| **panelPosition** | String/Object | Where to show the addon panel | `bottom` or `right` |
| **enableShortcuts** | Boolean | Enable/disable shortcuts | `true` |
| **isToolshown** | Boolean | Show/hide tool bar | `true` |
| **showToolbar** | Boolean | Show/hide tool bar | `true` |
| **theme** | Object | Storybook Theme, see next section | `undefined` |
| **selectedPanel** | String | Id to select an addon panel | `storybook/actions/panel` |
| **initialActive** | String | Select the default active tab on Mobile | `sidebar` or `canvas` or `addons` |

View File

@ -2,7 +2,7 @@
title: Install addons
---
Storybook has [hundreds of reusable addons](/addons) that are packaged as NPM modules. Let's walk through how to extend Storybook by installing and registering addons.
Storybook has [hundreds of reusable addons](https://storybook.js.org/addons) that are packaged as NPM modules. Let's walk through how to extend Storybook by installing and registering addons.
### Using addons

View File

@ -5,7 +5,7 @@ title: 'Introduction to addons'
Addons extend Storybook with features and integrations that are not built into the core. Most Storybook features are implemented as addons. For instance: [documentation](../writing-docs/introduction.md), [accessibility testing](https://github.com/storybookjs/storybook/tree/master/addons/a11y), [interactive controls](../essentials/controls.md), among others.
The [addon API](./addons-api.md) makes it easy for you to configure and customize Storybook in new ways. There are countless addons made by the community that unlock time-saving workflows.
Browse our [addon catalog](/addons) to install an existing addon or as inspiration for your own addon.
Browse our [addon catalog](https://storybook.js.org/addons) to install an existing addon or as inspiration for your own addon.
## Storybook basics

View File

@ -50,14 +50,23 @@ Once you've gone through the prompts, your `package.json` should look like:
### Build system
We'll need to add the necessary dependencies and make some adjustments. Run the following commands:
We'll need to add the necessary dependencies and make some adjustments. Run the following command to install the required dependencies:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-write-addon-install-dependencies.yarn.js.mdx',
'common/storybook-write-addon-install-dependencies.npm.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
Initialize a local Storybook instance to allow you to test your addon.
```shell
# Installs React and Babel CLI
yarn add react react-dom @babel/cli
# Adds Storybook:
npx sb init
npx storybook init
```
<div class="aside">
@ -246,4 +255,4 @@ To dive deeper, we recommend Storybook's [creating an addon](https://storybook.j
### Addon kit
To help you jumpstart the addon development, the Storybook maintainers created an [`addon-kit`](https://github.com/storybookjs/addon-kit), use it to bootstrap your next addon.
To help you jumpstart the addon development, the Storybook maintainers created an [`addon-kit`](https://github.com/storybookjs/addon-kit), use it to bootstrap your next addon.

View File

@ -2,7 +2,7 @@
title: 'Write a preset addon'
---
[Storybook preset addons](./addon-types.md#preset-addons) are grouped collections of `babel`, `webpack`, and `addons` configurations that support specific use cases in Storybook, such as typescript or MDX support.
[Storybook preset addons](./addon-types.md#preset-addons) are grouped collections of `babel`, `webpack`, and `addons` configurations that support specific use cases in Storybook, such as TypeScript or MDX support.
This doc covers the [presets API](#presets-api) and how to use the presets mechanism for [advanced configuration](#advanced-configuration).
@ -10,7 +10,7 @@ This doc covers the [presets API](#presets-api) and how to use the presets mecha
A preset is a set of hooks that is called by Storybook on initialization and can override configurations for `babel`, `webpack`, `addons`, and `entries`.
Each configuration has a similar signature, accepting a base configuration object and options, as in this webpack example:
Each configuration has a similar signature, accepting a base configuration object and options, as in this Webpack example:
<!-- prettier-ignore-start -->
@ -46,7 +46,7 @@ For example, Storybook's Mihtril support uses plugins internally and here's how
### Webpack
The webpack functions `webpack`, `webpackFinal`, and `managerWebpack` configure webpack.
The Webpack functions `webpack`, `webpackFinal`, and `managerWebpack` configure Webpack.
All functions take a [webpack4 configuration object](https://webpack.js.org/configuration/).
@ -62,11 +62,11 @@ For example, here is how Storybook automatically adopts `create-react-app`'s con
<!-- prettier-ignore-end -->
- `webpack` is applied to the preview config after it has been initialized by storybook
- `webpack` is applied to the preview config after it has been initialized by Storybook
- `webpackFinal` is applied to the preview config after all user presets have been applied
- `managerWebpack` is applied to the manager config
As of Storybook 6.3, Storybook can run with either `webpack4` or `webpack5` builder. If your addon needs to know which version of Webpack it's running inside, the version and the actual webpack instance itself are both available inside your preset:
As of Storybook 6.3, Storybook can run with either `webpack4` or `webpack5` builder. If your addon needs to know which version of Webpack it's running inside, the version and the actual Webpack instance itself are both available inside your preset:
<!-- prettier-ignore-start -->
@ -81,7 +81,7 @@ As of Storybook 6.3, Storybook can run with either `webpack4` or `webpack5` buil
### Manager entries
The addon config `managerEntries` allows you to add addons to Storybook from within a preset. For addons that require custom webpack/babel configuration, it is easier to install the preset, and it will take care of everything.
The addon config `managerEntries` allows you to add addons to Storybook from within a preset. For addons that require custom Webpack/Babel configuration, it is easier to install the preset, and it will take care of everything.
For example, the Storysource preset contains the following code:
@ -176,9 +176,9 @@ Entries are the place to register entry points for the preview. For example it c
## Advanced Configuration
The presets API is also more powerful than the [standard configuration options](../configure/webpack.md#extending-storybooks-webpack-config) available in Storybook, so it's also possible to use presets for more advanced configuration without actually publishing a preset yourself.
The presets API is also more powerful than the [standard configuration options](../builders/webpack.md#extending-storybooks-webpack-config) available in Storybook, so it's also possible to use presets for more advanced configuration without actually publishing a preset yourself.
For example, some users want to configure the webpack for Storybook's UI and addons ([issue](https://github.com/storybookjs/storybook/issues/4995)), but this is not possible using [standard webpack configuration](../configure/webpack.md#default-configuration) (it used to be possible before SB4.1). However, you can achieve this with a private preset.
For example, some users want to configure the Webpack for Storybook's UI and addons ([issue](https://github.com/storybookjs/storybook/issues/4995)), but this is not possible using [standard Webpack configuration](../builders/webpack.md#default-configuration) (it used to be possible before SB4.1). However, you can achieve this with a private preset.
If it doesn't exist yet, create a file `.storybook/main.js`:

View File

@ -4,6 +4,12 @@ title: 'CLI options'
Storybook comes with two CLI utilities: `start-storybook` and `build-storybook`.
<div class="aside">
Storybook collects completely anonymous data to help us improve user experience. Participation is optional, and you may [opt-out](../configure/telemetry.md#how-to-opt-out) if you'd not like to share any information.
</div>
Pass these commands the following options to alter Storybook's behavior.
## start-storybook
@ -18,10 +24,10 @@ Usage: start-storybook [options]
| `-V`, `--version` | Output the version number <br/>`start-storybook -V` |
| `-p`, `--port [number]` | Port to run Storybook <br/>`start-storybook -p 9009` |
| `-h`, `--host [string]` | Host to run Storybook <br/>`start-storybook -h my-host.com` |
| `-s`, `--static-dir` | **Deprecated** [see note](#static-dir-deprecation). Directory where to load static files from, comma-separated list <br/>`start-storybook -s public` |
| `-s`, `--static-dir` | **Deprecated** [see note](#static-dir-deprecation). Directory where to load static files from, comma-separated list<br/>`start-storybook -s public` |
| `-c`, `--config-dir [dir-name]` | Directory where to load Storybook configurations from <br/>`start-storybook -c .storybook` |
| `--https` | Serve Storybook over HTTPS. Note: You must provide your own certificate information. <br/>`start-storybook --https` |
| `--ssl-ca` | Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate) <br/>`start-storybook --ssl-ca my-certificate` |
| `--https` | Serve Storybook over HTTPS. Note: You must provide your own certificate information<br/>`start-storybook --https` |
| `--ssl-ca` | Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate)<br/>`start-storybook --ssl-ca my-certificate` |
| `--ssl-cert` | Provide an SSL certificate. (Required with --https)<br/>`start-storybook --ssl-cert my-ssl-certificate` |
| `--ssl-key` | Provide an SSL key. (Required with --https)<br/>`start-storybook --ssl-key my-ssl-key` |
| `--smoke-test` | Exit after successful start<br/>`start-storybook --smoke-test` |
@ -32,7 +38,8 @@ Usage: start-storybook [options]
| `--debug-webpack` | Display final webpack configurations for debugging purposes<br/>`start-storybook --debug-webpack` |
| `--webpack-stats-json` | Write Webpack Stats JSON to disk<br/>`start-storybook --webpack-stats-json /tmp/webpack-stats` |
| `--docs` | Starts Storybook in documentation mode. Learn more about it in [here](../writing-docs/build-documentation.md#preview-storybooks-documentation)<br/>`start-storybook --docs` |
| `--no-manager-cache` | Disables Storybook's manager caching mechanism. See note below.<br/>`start-storybook --no-manager-cache` |
| `--no-manager-cache` | Disables Storybook's manager caching mechanism. See note below<br/>`start-storybook --no-manager-cache` |
| `--disable-telemetry` | Disables Storybook's telemetry. Learn more about it [here](../configure/telemetry.md)<br/>`start-storybook --disable-telemetry` |
<div class="aside">
💡 The flag <code>--no-manager-cache</code> disables the internal caching of Storybook and can severely impact your Storybook loading time, so only use it when you need to refresh Storybook's UI, such as when editing themes.
@ -54,15 +61,16 @@ Usage: build-storybook [options]
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-h`, `--help` | Output usage information<br/>`build-storybook --help` |
| `-V`, `--version` | Output the version number<br/>`build-storybook -V` |
| `-s`, `--static-dir` | **Deprecated** [see note](#static-dir-deprecation). Directory where to load static files from, comma-separated list<br/>`build-storybook -s public` |
| `-s`, `--static-dir` | **Deprecated** [see note](#static-dir-deprecation).<br/> Directory where to load static files from, comma-separated list<br/>`build-storybook -s public` |
| `-o`, `--output-dir [dir-name]` | Directory where to store built files<br/>`build-storybook -o /my-deployed-storybook` |
| `-c`, `--config-dir [dir-name]` | Directory where to load Storybook configurations from<br/>`build-storybook -c .storybook` |
| `--loglevel [level]` | Controls level of logging during build. Can be one of: [silly, verbose, info (default), warn, error, silent]<br/>`build-storybook --loglevel warn` |
| `--loglevel [level]` | Controls level of logging during build.<br/> Available options: `silly`, `verbose`, `info` (default), `warn`, `error`, `silent`<br/>`build-storybook --loglevel warn` |
| `--quiet` | Suppress verbose build output<br/>`build-storybook --quiet` |
| `--no-dll` | Do not use dll reference (no-op)<br/>`build-storybook --no-dll` |
| `--debug-webpack` | Display final webpack configurations for debugging purposes<br/>`build-storybook --debug-webpack` |
| `--webpack-stats-json` | Write Webpack Stats JSON to disk<br/>`build-storybook --webpack-stats-json /my-storybook/webpack-stats` |
| `--docs` | Builds Storybook in documentation mode. Learn more about it in [here](../writing-docs/build-documentation.md#publish-storybooks-documentation)<br/>`build-storybook --docs` |
| `--disable-telemetry` | Disables Storybook's telemetry. Learn more about it [here](../configure/telemetry.md).<br/>`build-storybook --disable-telemetry` |
<div class="aside">
💡 If you're using npm instead of yarn to publish Storybook, the commands work slightly different. For example, <code>npm run build-storybook -- -o ./path/to/build</code>.

View File

@ -0,0 +1,163 @@
---
title: 'Builder API'
---
Storybook is architected to support multiple builders, including [Webpack](https://webpack.js.org/), [Vite](https://vitejs.dev/), and [ESBuild](https://esbuild.github.io/). The builder API is the set of interfaces you can use to add a new builder to Storybook.
![Storybook builders](./storybook-builders.png)
## How do builders work?
In Storybook, a builder is responsible for compiling your components and stories into JS bundles that run in the browser. A builder also provides a development server for interactive development and a production mode for optimized bundles.
To opt into a builder, the user must add it as a dependency and then edit their configuration file (`.storybook/main.js`) to enable it. For example, with the Vite builder:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-vite-builder-install.yarn.js.mdx',
'common/storybook-vite-builder-install.npm.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-vite-builder-register.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
## Builder API
In Storybook, every builder must implement the following [API](https://github.com/storybookjs/storybook/blob/next/lib/core-common/src/types.ts#L170-L189), exposing the following configuration options and entry points:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-builder-api-interface.ts.mdx',
]}
/>
<!-- prettier-ignore-end -->
In development mode, the `start` API call is responsible for initializing the development server to monitor the file system for changes (for example, components and stories) then execute a hot module reload in the browser.
It also provides a **bail** function to allow the running process to end gracefully, either via user input or error.
In production, the `build` API call is responsible for generating a static Storybook build, storing it by default in the `storybook-static` directory if no additional configuration is provided. The generated output should contain everything the user needs to view its Storybook by opening either the `index.html` or `iframe.html` in a browser with no other processes running.
## Implementation
Under the hood, a builder is responsible for serving/building the preview `iframe`, which has its own set of requirements. To fully support Storybook, including the [Essential addons](../writing-stories/introduction.md) that ship with Storybook, it must consider the following.
### Import stories
The `stories` configuration field enables story loading in Storybook. It defines an array of file globs containing the physical location of the component's stories. The builder must be able to load those files and monitor them for changes and update the UI accordingly.
### Provide configuration options
By default, Storybook's configuration is handled in a dedicated file (`storybook/main.js|ts`), giving the user the option to customize it to suit its needs. The builder should also provide its own configuration support through additional fields or some other builder-appropriate mechanism. For example:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-builder-api-configuration-options.ts.mdx',
]}
/>
<!-- prettier-ignore-end -->
### Handle preview.js exports
The [`preview.js`](../configure/overview.md#configure-story-rendering) configuration file allows users to control how the story renders in the UI. This is provided via the [decorators](../writing-stories/decorators.md) named export. When Storybook starts, it converts these named exports into internal API calls via virtual module entry, for example, `addDecorator()`. The builder must also provide a similar implementation. For example:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-builder-api-preview-exports.ts.mdx',
]}
/>
<!-- prettier-ignore-end -->
### MDX support
[Storybook's Docs](../writing-docs/introduction.md) includes the ability to author stories/documentation in MDX using a Webpack loader. The builder must also know how to interpret MDX and invoke Storybook's special extensions. For example:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-builder-api-mdx.ts.mdx',
]}
/>
<!-- prettier-ignore-end -->
### Generate source code snippets
Storybook annotates components and stories with additional metadata related to their inputs to automatically generate interactive controls and documentation. Currently, this is provided via Webpack loaders/plugins. The builder must re-implement this to support those features.
### Generate a static build
One of Storybook's core features it's the ability to generate a static build that can be [published](../sharing/publish-storybook.md) to a web hosting service. The builder must also be able to provide a similar mechanism. For example:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-builder-api-build-server.ts.mdx',
]}
/>
<!-- prettier-ignore-end -->
### Development server integration
By default, when Storybook starts in development mode, it relies on its internal development server. The builder needs to be able to integrate with it. For example:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-builder-api-dev-server.ts.mdx',
]}
/>
<!-- prettier-ignore-end -->
### Shutdown the development server
The builder must provide a way to stop the development server once the process terminates; this can be via user input or error. For example:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-builder-api-shutdown-server.ts.mdx',
]}
/>
<!-- prettier-ignore-end -->
### HMR support
While running in development mode, the builder's development server must be able to reload the page once a change happens, either in a story, component, or helper function.
### More information
This area is under rapid development, so the documented is still in progress and subject to change. If you are interested in writing your builder, we encourage you to check [webpack](https://github.com/storybookjs/storybook/tree/next/lib/builder-webpack4), [Vite](https://github.com/storybookjs/builder-vite), and Modern Web's [dev-server-storybook](https://github.com/modernweb-dev/web/blob/master/packages/dev-server-storybook/src/serve/storybookPlugin.ts) source code. In addition, we have a wonderful contributor community on [Storybook Discord](https://discord.gg/storybook) if you have questions. Ping us in the [#contributing](https://discord.com/channels/486522875931656193/839297503446695956) channel.
#### Learn more about builders
- [Vite builder](./vite.md) for bundling with Vite
- [Webpack builder](./webpack.md) for bundling with Webpack
- Builder API for building a Storybook builder

24
docs/builders/overview.md Normal file
View File

@ -0,0 +1,24 @@
---
title: 'Builders'
---
Storybook, at its core, is powered by builders such as Webpack and Vite. These builders spin up a development environment, compile your code—Javascript, CSS, and MDX—into an executable bundle and update the browser in real-time.
![Storybook builder overview](./storybook-builder-workflow.png)
## CLI basics
Before diving into setting up Storybook's builders, let's look at how the CLI configures them. When you initialize Storybook (via `npx storybook init`), the CLI automatically detects which builder to use based on your application. For example, if you're working with Vite, it will install the Vite builder. If you're working with Webpack, it installs the Webpack builder based on your current version.
Additionally, you can also provide a flag to Storybook's CLI and specify the builder you want to use:
```shell
npx storybook init --builder <webpack4 | webpack5 | vite>
```
## Manual setup
Storybook uses the Webpack 4 builder by default if you don't specify one. If you want to use a different builder in your application, these docs detail how you can set up Storybook's supported builders.
- [**Vite builder**](./vite.md) for bundling your stories with Vite with near-instant HMR.
- [**Webpack**](./webpack.md) for bundling your stories with Webpack with improved performance

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

122
docs/builders/vite.md Normal file
View File

@ -0,0 +1,122 @@
---
title: 'Vite'
---
Storybook Vite builder bundles your components and stories with [Vite](https://vitejs.dev/), a fast ESM bundler.
- For applications built with Vite: it allows reusing the existing configuration in Storybook.
- For applications built with Webpack: it provides faster startup and refresh times, with the disadvantage that your component's execution environment differs from your application.
## Setup
If you ran `npx storybook init` to include Storybook in your Vite application, the builder is already installed and configured for you. If you want, you can also opt into it manually.
Run the following command to install the builder.
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-vite-builder-install.yarn.js.mdx',
'common/storybook-vite-builder-install.npm.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
Update your Storybook configuration (in `.storybook/main.js|ts`) to include the builder.
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-vite-builder-register.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
## Configuration
Out of the box, Storybook's Vite builder includes a set of configuration defaults for the supported frameworks. You can also fine-tune them or override them to match your existing configuration as, by default, the builder does not read your `vite.config.js` file. For example, if you need to set up aliasing, you can adjust your Storybook configuration file (`.storybook/main.js|ts`) and provide the following:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-vite-builder-aliasing.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
The asynchronous function`viteFinal` receives a `config` object with the default builder configuration and returns the updated configuration with the defined alias.
You can also override the builder's configuration based on the environment. For instance, if you need to provide a custom configuration for development purposes and another for production, you can extend the default configuration as follows:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-vite-builder-config-env.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
### Svelte configuration
If you're working with Svelte's Vite plugin ([`vite-plugin-svelte`](https://github.com/sveltejs/vite-plugin-svelte/tree/main/packages/vite-plugin-svelte)), you can extend your existing configuration and include an additional `SvelteOptions` object to customize it. For example:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-vite-builder-svelte-plugin.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
### TypeScript
If you need, you can also configure Storybook's Vite builder using TypeScript. Rename your `.storybook/main.js` to `.storybook/main.ts` and adjust it as follows:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-vite-builder-ts-configure.ts.mdx',
]}
/>
<!-- prettier-ignore-end -->
---
## Troubleshooting
### Working directory not being detected
By default, the Vite builder enables Vite's [`server.fs.strict`](https://vitejs.dev/config/#server-fs-strict) option for increased security, defining the project's `root` to Storybook's configuration directory
If you need to override it, you can use the `viteFinal` function and adjust it.
### ArgTypes are not generated automatically
Currently, [automatic argType inference](../api/argtypes.md#automatic-argtype-inference) is only available for React and Vue3. With React, the Vite builder defaults to `react-docgen-typescript` if TypeScript is listed as a dependency. If you run into any issues, you can revert to `react-docgen` by updating your Storybook configuration file as follows:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-vite-builder-react-docgen.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
#### Learn more about builders
- Vite builder for bundling with Vite
- [Webpack builder](./webpack.md) for bundling with Webpack
- [Builder API](./builder-api.md) for building a Storybook builder

221
docs/builders/webpack.md Normal file
View File

@ -0,0 +1,221 @@
---
title: 'Webpack'
---
Storybook displays your components in a custom web application built using [Webpack](https://webpack.js.org/). Webpack is a complex tool, but our default configuration is intended to cover most use cases. [Addons](https://storybook.js.org/addons/) are also available that extend the configuration for other everyday use cases.
You can customize Storybook's Webpack setup by providing a `webpackFinal` field in [`.storybook/main.js`](../configure/overview.md#configure-your-storybook-project) file.
The value should be an async function that receives a Webpack config and eventually returns a Webpack config.
### Default configuration
By default, Storybook's Webpack configuration will allow you to:
#### Import images and other static files
You can import images and other local files and have them built into the Storybook:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/my-component-story-import-static-asset.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
#### Import JSON as JavaScript
You can import `.json` files and have them expanded to a JavaScript object:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/my-component-story-import-json.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
If you want to know the exact details of the Webpack config, the best way is to run either of the following commands:
For development mode:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-debug-webpack-dev.yarn.js.mdx',
'common/storybook-debug-webpack-dev.npm.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
For production mode:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-debug-webpack-prod.yarn.js.mdx',
'common/storybook-debug-webpack-prod.npm.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
### Code splitting
Starting with Storybook 6.4, [code splitting](https://v4.webpack.js.org/guides/code-splitting/) is supported through a configuration flag. Update your Storybook configuration and add the `storyStoreV7` flag:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-on-demand-story-loading.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
When you start your Storybook, you'll see an improvement in loading times. Read more about it in the [announcement post](https://storybook.js.org/blog/storybook-on-demand-architecture/) and the [configuration documentation](../configure/overview.md#configure-your-storybook-project).
### Webpack 5
Storybook builds your project with Webpack 4 by default. If your project uses Webpack 5, you can opt into the Webpack 5 builder by installing the required dependencies (i.e., `@storybook/builder-webpack5`, `@storybook/manager-webpack5`) and update your Storybook configuration as follows:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-main-webpack5.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
Once you are using Webpack 5, you can further opt into some features to optimize your build:
#### Lazy Compilation
Storybook supports Webpack's experimental [lazy compilation](https://webpack.js.org/configuration/experiments/#experimentslazycompilation) feature, via the `lazyCompilation` builder flag:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-main-webpack5-lazyCompilation.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
This feature applies in development mode, and will mean your Storybook will start up faster, at the cost of slightly slower browsing time when you change stories.
#### Filesystem Caching
Storybook supports Webpack's [filesystem caching](https://webpack.js.org/configuration/cache/#cachetype) feature, via the `fsCache` builder flag:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-main-webpack5-fsCache.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
This feature will mean build output is cached between runs of Storybook, speeding up subsequent startup times.
### Extending Storybooks Webpack config
To extend the above configuration, use the `webpackFinal` field of [`.storybook/main.js`](../configure/overview.md#configure-your-storybook-project).
The value should export a `function`, which will receive the default config as its first argument. The second argument is an options object from Storybook, and this will have information about where config came from, whether we're in production or development mode, etc.
For example, if you wanted to add [Sass](https://sass-lang.com/) support, you can adjust your configuration as such:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-main-add-sass-config.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
Storybook uses the config returned from the above function to render your components in Storybook's "preview" iframe. Note that Storybook has an entirely separate Webpack config for its UI (also referred to as the "manager"), so the customizations you make only apply to the rendering of your stories, i.e., you can completely replace `config.module.rules` if you want.
Nevertheless, edit `config` with care. Make sure to preserve the following config options:
- **entry**
- **output**
Furthermore, `config` requires the `HtmlWebpackplugin` to generate the preview page, so rather than overwriting `config.plugins` you should probably append to it (or overwrite it with care), see [the following issue](https://github.com/storybookjs/storybook/issues/6020) for examples on how to handle this:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-main-simplified-config.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
Finally, if your custom Webpack config uses a loader that does not explicitly include specific file extensions via the `test` property, in that case, it is necessary to `exclude` the `.ejs` file extension from that loader.
If you're using a non-standard Storybook config directory, you should put `main.js` there instead of `.storybook` and update the `include` path to ensure it resolves to your project root.
### Using your existing config
Suppose you have an existing Webpack config for your project and want to reuse this app's configuration. In that case, you can import your main Webpack config into Storybook's [`.storybook/main.js`](../configure/overview.md#configure-your-storybook-project) and merge both:
The following code snippet shows how you can replace the loaders from Storybook with the ones from your app's `webpack.config.js`:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-main-using-existing-config.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
<div class="aside">
💡 Projects initialized via generators (e.g, Vue CLI) may require that you import their own Webpack config file (i.e., <code>/projectRoot/node_modules/@vue/cli-service/webpack.config.js</code>) to use a certain feature with Storybook. For other generators, make sure to check the documentation for instructions.
</div>
### TypeScript Module Resolution
When working with TypeScript projects, the default Webpack configuration may fail to resolve module aliases defined in your [`tsconfig` file](https://www.typescriptlang.org/tsconfig). To work around this issue you may use [`tsconfig-paths-webpack-plugin`](https://github.com/dividab/tsconfig-paths-webpack-plugin#tsconfig-paths-webpack-plugin) while [extending Storybook's Webpack config](#extending-storybooks-webpack-config) like:
<!-- prettier-ignore-start -->
<CodeSnippets
paths={[
'common/storybook-main-ts-module-resolution.js.mdx',
]}
/>
<!-- prettier-ignore-end -->
<div class="aside">
💡 Learn more about Storybook's <a href="../configure/typescript">built-in TypeScript support</a> or see <a href="https://github.com/storybookjs/storybook/issues/14087">this issue</a> for more information.
</div>
#### Learn more about builders
- [Vite builder](./vite.md) for bundling with Vite
- Webpack builder for bundling with Webpack
- [Builder API](./builder-api.md) for building a Storybook builder

View File

@ -81,7 +81,7 @@ For detailed instructions on migrating from `V6` mode, please see [MIGRATION.md]
If your app does not include a babelrc file, and you need one, you can create it by running the following command in your project directory:
```sh
npx sb@next babelrc
npx storybook@next babelrc
```
Once the command completes, you should have a `.babelrc.json` file created in the root directory of your project, similar to the following example:

View File

@ -42,7 +42,11 @@ Additionally, if you need Storybook specific styles that are separate from your
```
### Nx with Angular 13
If you're working with Storybook and [NX libraries](https://nx.dev/structure/library-types), you can extend your project's configuration (i.e., `project.json`) and provide the application's styles. For example:
If you're working with Storybook and [Nx libraries](https://nx.dev/structure/library-types),
you can extend your project's configuration (i.e., `project.json`) and provide the application's styles.
For earlier Nx versions (prior to `14.1.8`), your configuration would look like this:
```json
"build-storybook": {
@ -56,11 +60,35 @@ If you're working with Storybook and [NX libraries](https://nx.dev/structure/lib
},
"projectBuildConfig": "example-lib:build-storybook",
"styles": ["apps/example-app/src/styles.scss"]
}
}
```
Starting with version `14.1.8`, Nx uses the Storybook builder directly, which means any configuration supplied to the builder also applies to the NX setup. If you're working with a library, you'll need to configure the styling options ( e.g., preprocessors) inside the `build-storybook` `options` configuration object. For example:
```json
"storybook": {
"executor": "@storybook/angular:start-storybook",
"options": {
"configDir": "apps/example-lib/.storybook",
"browserTarget": "example-lib:build-storybook",
},
},
"configurations": {
"ci": {
"quiet": true
"build-storybook": {
"executor": "@storybook/angular:build-storybook",
"outputs": ["{options.outputPath}"],
"options": {
"outputDir": "dist/storybook/example-lib",
"configDir": "apps/example-lib/.storybook",
"browserTarget": "example-lib:build-storybook",
"styles": [".storybook/custom-styles.scss"],
"stylePreprocessorOptions": {
"includePaths": [
"libs/design-system/src/lib"
]
}
}
}
}
```
When Nx runs, it will load Storybook's configuration and styling based on the `storybook`'s [`browserTarget`](https://nx.dev/storybook/extra-topics-for-angular-projects#setting-up-browsertarget).

Some files were not shown because too many files have changed in this diff Show More