diff --git a/.github/workflows/generate-sandboxes-main.yml b/.github/workflows/generate-sandboxes-main.yml deleted file mode 100644 index cc17376c57de..000000000000 --- a/.github/workflows/generate-sandboxes-main.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Generate and push sandboxes (main) - -on: - schedule: - - cron: '2 2 */1 * *' - workflow_dispatch: - # To test fixes on push rather than wait for the scheduling, do the following: - # 1. Uncomment the lines below and add your branch. - # push: - # branches: - # - - # 2. change the "ref" value to in the actions/checkout step below. - # 3. πŸ‘‰ DON'T FORGET TO UNDO THE VALUES BACK TO `main` BEFORE YOU MERGE YOUR CHANGES! - -jobs: - generate: - runs-on: ubuntu-latest - env: - YARN_ENABLE_IMMUTABLE_INSTALLS: false - CLEANUP_SANDBOX_NODE_MODULES: true - steps: - - uses: actions/checkout@v3 - with: - ref: main - - uses: actions/setup-node@v3 - with: - node-version-file: '.nvmrc' - - name: Setup git user - run: | - git config --global user.name "Storybook Bot" - git config --global user.email "bot@storybook.js.org" - - name: Install dependencies - run: | - cd ./scripts - node --experimental-modules ./check-dependencies.js - cd .. - - name: Compile Storybook libraries - run: yarn task --task compile --start-from=auto --no-link - - name: Publishing to local registry - run: yarn local-registry --publish - working-directory: ./code - - name: Running local registry - run: yarn local-registry --open & - working-directory: ./code - - name: Wait for registry - run: yarn wait-on tcp:127.0.0.1:6001 - working-directory: ./code - - name: Generate - run: yarn generate-sandboxes --local-registry - working-directory: ./code - - name: Publish - run: yarn publish-sandboxes --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT}}@github.com/storybookjs/sandboxes.git --push --branch=main - working-directory: ./code - - name: The job has failed - if: ${{ failure() || cancelled() }} - env: - DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }} - uses: Ilshidur/action-discord@master - with: - args: 'The generation of sandboxes in the **main** branch has failed. [View Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})' diff --git a/.github/workflows/generate-sandboxes-next.yml b/.github/workflows/generate-sandboxes-next.yml deleted file mode 100644 index 2c0b592d024f..000000000000 --- a/.github/workflows/generate-sandboxes-next.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Generate and push sandboxes (next) - -on: - schedule: - - cron: '2 2 */1 * *' - workflow_dispatch: - # To test fixes on push rather than wait for the scheduling, do the following: - # 1. Uncomment the lines below and add your branch. - # push: - # branches: - # - - # 2. change the "ref" value to in the actions/checkout step below. - # 3. πŸ‘‰ DON'T FORGET TO UNDO THE VALUES BACK TO `next` BEFORE YOU MERGE YOUR CHANGES! - -jobs: - generate: - runs-on: ubuntu-latest - env: - YARN_ENABLE_IMMUTABLE_INSTALLS: false - CLEANUP_SANDBOX_NODE_MODULES: true - steps: - - uses: actions/checkout@v3 - with: - ref: next - - uses: actions/setup-node@v3 - with: - node-version-file: '.nvmrc' - - name: Setup git user - run: | - git config --global user.name "Storybook Bot" - git config --global user.email "bot@storybook.js.org" - - name: Install dependencies - run: | - cd ./scripts - node --experimental-modules ./check-dependencies.js - cd .. - - name: Compile Storybook libraries - run: yarn task --task compile --start-from=auto --no-link - - name: Publishing to local registry - run: yarn local-registry --publish - working-directory: ./code - - name: Running local registry - run: yarn local-registry --open & - working-directory: ./code - - name: Wait for registry - run: yarn wait-on tcp:127.0.0.1:6001 - working-directory: ./code - - name: Generate - run: yarn generate-sandboxes --local-registry --debug - working-directory: ./code - - name: Publish - run: yarn publish-sandboxes --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT}}@github.com/storybookjs/sandboxes.git --push --branch=next - working-directory: ./code - - name: The job has failed - if: ${{ failure() || cancelled() }} - env: - DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }} - uses: Ilshidur/action-discord@master - with: - args: 'The generation of sandboxes in the **next** branch has failed. [View Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})' diff --git a/.github/workflows/generate-sandboxes.yml b/.github/workflows/generate-sandboxes.yml new file mode 100644 index 000000000000..88dc9e21bb41 --- /dev/null +++ b/.github/workflows/generate-sandboxes.yml @@ -0,0 +1,127 @@ +name: Generate and publish sandboxes + +on: + schedule: + - cron: "2 2 */1 * *" + workflow_dispatch: + # To test fixes on push rather than wait for the scheduling, do the following: + # 1. Uncomment the lines below and add your branch. + # push: + # branches: + # - + # 2. Change the "ref" value to in the actions/checkout step below. + # 3. Comment out the whole "generate-main" job starting at line 77 + # 4. πŸ‘‰ DON'T FORGET TO UNDO THE STEPS BEFORE YOU MERGE YOUR CHANGES! + +env: + YARN_ENABLE_IMMUTABLE_INSTALLS: "false" + CLEANUP_SANDBOX_NODE_MODULES: "true" + +defaults: + run: + working-directory: ./code + +jobs: + generate-next: + name: Generate to next + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: next + + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - name: Setup git user + run: | + git config --global user.name "storybook-bot" + git config --global user.email "32066757+storybook-bot@users.noreply.github.com" + + - name: Install dependencies + working-directory: ./scripts + run: node --experimental-modules ./check-dependencies.js + + - name: Compile Storybook libraries + run: yarn task --task compile --start-from=auto --no-link + + - name: Publish to local registry + run: yarn local-registry --publish + + - name: Run local registry + run: yarn local-registry --open & + + - name: Wait for registry + run: yarn wait-on tcp:127.0.0.1:6001 + + - name: Generate + id: generate + run: yarn generate-sandboxes --local-registry + + - name: Publish + # publish sandboxes even if the generation fails, as some sandboxes might have been generated successfully + if: ${{ !cancelled() }} + run: yarn publish-sandboxes --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT }}@github.com/storybookjs/sandboxes.git --push --branch=next + + - name: Report failure to Discord + if: failure() + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }} + uses: Ilshidur/action-discord@master + with: + args: | + The generation of some or all sandboxes on the **next** branch has failed. + [See the job summary for details](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + + generate-main: + name: Generate to main + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: main + + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - name: Setup git user + run: | + git config --global user.name "storybook-bot" + git config --global user.email "32066757+storybook-bot@users.noreply.github.com" + + - name: Install dependencies + working-directory: ./scripts + run: node --experimental-modules ./check-dependencies.js + + - name: Compile Storybook libraries + run: yarn task --task compile --start-from=auto --no-link + + - name: Publish to local registry + run: yarn local-registry --publish + + - name: Run local registry + run: yarn local-registry --open & + + - name: Wait for registry + run: yarn wait-on tcp:127.0.0.1:6001 + + - name: Generate + id: generate + run: yarn generate-sandboxes --local-registry + + - name: Publish + # publish sandboxes even if the generation fails, as some sandboxes might have been generated successfully + if: ${{ !cancelled() }} + run: yarn publish-sandboxes --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT }}@github.com/storybookjs/sandboxes.git --push --branch=main + + - name: Report failure to Discord + if: failure() + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }} + uses: Ilshidur/action-discord@master + with: + args: | + The generation of some or all sandboxes on the **main** branch has failed. + [See the job summary for details](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) diff --git a/CHANGELOG.md b/CHANGELOG.md index 649310bf1c11..6a1978abc0d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 8.0.0 + +#### Storybook 8.0 is here + +It brings major improvements to Storybook's feature set for testing and documentation, with strengthened framework support across React, Vue, Angular, web-components, Svelte, and more. + +- 🩻 Built-in visual testing +- βš›οΈ React Server Component support +- πŸŽ›οΈ Improved controls for React and Vue projects +- ⚑️ Improved Vite architecture, Vitest testing, and Vite 5 support +- πŸ§ͺ 2-4x faster Storybooks for testing +- ✨ Refreshed desktop UI +- πŸ“² Rebuilt mobile UX +- πŸ™…β€β™€οΈ No more React requirement in non-React projects + +Please checkout our [Migration Guide](https://storybook.js.org/docs/8.0/migration-guide) to upgrade from earlier versions of Storybook. To see a comprehensive list of changes that went into 8.0, you can refer to the [8.0 prerelease changelogs](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.prerelease.md). + ## 7.6.17 - Addon-docs: Fix Table of Contents heading leak - [#23677](https://github.com/storybookjs/storybook/pull/23677), thanks [@vmizg](https://github.com/vmizg)! @@ -105,91 +122,91 @@ Storybook 7.6 is here with increased performance and much more! List of all updates - - Actions: Attach spies on actions across stories when defined in meta - [#24451](https://github.com/storybookjs/storybook/pull/24451), thanks [@kasperpeulen](https://github.com/kasperpeulen)! - - Actions: Fix `@storybook/core-events/preview-errors` dependency missing for Yarn PnP - [#24973](https://github.com/storybookjs/storybook/pull/24973), thanks [@JReinhold](https://github.com/JReinhold)! - - Actions: Fix missing crypto module crashing React Native - [#24546](https://github.com/storybookjs/storybook/pull/24546), thanks [@dannyhw](https://github.com/dannyhw)! - - Actions: Warn on implicit actions - [#24856](https://github.com/storybookjs/storybook/pull/24856), thanks [@kasperpeulen](https://github.com/kasperpeulen)! - - Addon A11y: Avoid CSP issue - [#24477](https://github.com/storybookjs/storybook/pull/24477), thanks [@Marklb](https://github.com/Marklb)! - - Addon: Move Visual Test addon to the code directory - [#24771](https://github.com/storybookjs/storybook/pull/24771), thanks [@cdedreuille](https://github.com/cdedreuille)! - - Addons, core: Make `react` and Storybook packages `devDependencies` where possible - [#24676](https://github.com/storybookjs/storybook/pull/24676), thanks [@JReinhold](https://github.com/JReinhold)! - - Addons, core: Make `react` and Storybook packages `devDependencies` where possible - ATTEMPT 2 - [#24834](https://github.com/storybookjs/storybook/pull/24834), thanks [@JReinhold](https://github.com/JReinhold)! - - Angular: Add source-map option to builder - [#24466](https://github.com/storybookjs/storybook/pull/24466), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Angular: Handle nested module metadata - [#24798](https://github.com/storybookjs/storybook/pull/24798), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Angular: Include object configured styles - [#24768](https://github.com/storybookjs/storybook/pull/24768), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Babel: Update all @babel/* dependencies - [#24610](https://github.com/storybookjs/storybook/pull/24610), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - CLI: Add "doctor" command - [#22236](https://github.com/storybookjs/storybook/pull/22236), thanks [@yannbf](https://github.com/yannbf)! - - CLI: Add @storybook/addon-designs to non-core list - [#24507](https://github.com/storybookjs/storybook/pull/24507), thanks [@yannbf](https://github.com/yannbf)! - - CLI: Ensure errors with opening the browser are caught - [#24668](https://github.com/storybookjs/storybook/pull/24668), thanks [@xueyawei](https://github.com/xueyawei)! - - CLI: Ignore `addon-onboarding` when checking versions - [#24634](https://github.com/storybookjs/storybook/pull/24634), thanks [@JReinhold](https://github.com/JReinhold)! - - CLI: Use @storybook/test in template stories - [#24393](https://github.com/storybookjs/storybook/pull/24393), thanks [@yannbf](https://github.com/yannbf)! - - Controls: Improve accessibility of BooleanControl for screen readers - [#24418](https://github.com/storybookjs/storybook/pull/24418), thanks [@danielmarcano](https://github.com/danielmarcano)! - - Core-Server: Ignore all node_module folders for watchpack - [#24553](https://github.com/storybookjs/storybook/pull/24553), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Core: Add deprecation notice for Vite + CommonJS - [#23950](https://github.com/storybookjs/storybook/pull/23950), thanks [@JReinhold](https://github.com/JReinhold)! - - Core: Detect no matching export error in storybook start and build - [#24877](https://github.com/storybookjs/storybook/pull/24877), thanks [@yannbf](https://github.com/yannbf)! - - Core: Fix `useStoryPrepared` hook failing with `undefined` data - [#22631](https://github.com/storybookjs/storybook/pull/22631), thanks [@SpookyJelly](https://github.com/SpookyJelly)! - - Core: Fix pnp support when cache dir is outside working dir - [#24572](https://github.com/storybookjs/storybook/pull/24572), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Core: Fix post message channel location.search access for React Native - [#24545](https://github.com/storybookjs/storybook/pull/24545), thanks [@dannyhw](https://github.com/dannyhw)! - - Core: Gracefully handle error when parsing preview.js file - [#24858](https://github.com/storybookjs/storybook/pull/24858), thanks [@yannbf](https://github.com/yannbf)! - - Core: Make warnOnIncompatibleAddons fault-tolerant - [#24880](https://github.com/storybookjs/storybook/pull/24880), thanks [@taozhou-glean](https://github.com/taozhou-glean)! - - Dependencies: Fix Yarn 4 failing to install due to jscodeshift dependency issue - [#24914](https://github.com/storybookjs/storybook/pull/24914), thanks [@samvv](https://github.com/samvv)! - - Dependencies: Update @babel/traverse and @babel/core to fix vulnerability - [#24670](https://github.com/storybookjs/storybook/pull/24670), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Dependencies: Update browserify-sign transitive dependency - [#24674](https://github.com/storybookjs/storybook/pull/24674), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Dependencies: Update jscodeshift to v0.15.1 - [#24882](https://github.com/storybookjs/storybook/pull/24882), thanks [@epreston](https://github.com/epreston)! - - Dependencies: Update nx dependencies to v17 - [#24671](https://github.com/storybookjs/storybook/pull/24671), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Doc Blocks: Add support for `of` prop to `Primary` block - [#23849](https://github.com/storybookjs/storybook/pull/23849), thanks [@Wilson2k](https://github.com/Wilson2k)! - - Doc Blocks: Remove `defaultProps` in `Stories` block - [#24506](https://github.com/storybookjs/storybook/pull/24506), thanks [@WouterK12](https://github.com/WouterK12)! - - Docs: Changes corresponding to docs design updates - [#24925](https://github.com/storybookjs/storybook/pull/24925), thanks [@kylegach](https://github.com/kylegach)! - - Maintenance: Split renderers preview entrypoints - [#24623](https://github.com/storybookjs/storybook/pull/24623), thanks [@ndelangen](https://github.com/ndelangen)! - - Manager: Update `store.settings.lastTrackedStoryId` - [#24115](https://github.com/storybookjs/storybook/pull/24115), thanks [@rashidshamloo](https://github.com/rashidshamloo)! - - ManagerAPI: Fix setting status without index, crashes storybook - [#24866](https://github.com/storybookjs/storybook/pull/24866), thanks [@ndelangen](https://github.com/ndelangen)! - - ManagerBuilder: Fix `"type": "commonjs"` compatibility - [#24534](https://github.com/storybookjs/storybook/pull/24534), thanks [@ndelangen](https://github.com/ndelangen)! - - Next.js: Add avif support - [#24611](https://github.com/storybookjs/storybook/pull/24611), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Next.js: Add back image context CommonJS export - [#24885](https://github.com/storybookjs/storybook/pull/24885), thanks [@martinnabhan](https://github.com/martinnabhan)! - - Next.js: Add experimental SWC support - [#24852](https://github.com/storybookjs/storybook/pull/24852), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Next.js: Fix Fast Refresh config for SWC mode - [#24991](https://github.com/storybookjs/storybook/pull/24991), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Next.js: Fix forwarding ref for Image component - [#24648](https://github.com/storybookjs/storybook/pull/24648), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Next.js: Fix import path in swc loader - [#24922](https://github.com/storybookjs/storybook/pull/24922), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Next.js: Fix react-docgen usage with preset-env settings - [#24993](https://github.com/storybookjs/storybook/pull/24993), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Next.js: Remove duplicate Fast Refresh plugin init - [#24963](https://github.com/storybookjs/storybook/pull/24963), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - React: Upgrade `react-docgen` to v7 - [#24530](https://github.com/storybookjs/storybook/pull/24530), thanks [@shilman](https://github.com/shilman)! - - ReactNative: Fix missing assert dep in docs-tools - [#24732](https://github.com/storybookjs/storybook/pull/24732), thanks [@dannyhw](https://github.com/dannyhw)! - - Svelte: Fix decorators always running twice - [#24921](https://github.com/storybookjs/storybook/pull/24921), thanks [@paoloricciuti](https://github.com/paoloricciuti)! - - Svelte: Fix source with decorators always showing the `SlotDecorator` component - [#24800](https://github.com/storybookjs/storybook/pull/24800), thanks [@JReinhold](https://github.com/JReinhold)! - - SvelteKit: Add experimental page and navigation mocking - [#24795](https://github.com/storybookjs/storybook/pull/24795), thanks [@paoloricciuti](https://github.com/paoloricciuti)! - - SvelteKit: Default to log an action for `goto`, `invalidate` and `invalidateAll` - [#24955](https://github.com/storybookjs/storybook/pull/24955), thanks [@paoloricciuti](https://github.com/paoloricciuti)! - - SWC: Add settings for react and preact - [#24805](https://github.com/storybookjs/storybook/pull/24805), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Test Build: Add env-variable support to `--test` CLI-flag - [#24862](https://github.com/storybookjs/storybook/pull/24862), thanks [@ndelangen](https://github.com/ndelangen)! - - Test Build: Add tests and rename to camelCase - [#24911](https://github.com/storybookjs/storybook/pull/24911), thanks [@ndelangen](https://github.com/ndelangen)! - - Test Build: Disable composition when `--test` is `true` - [#24799](https://github.com/storybookjs/storybook/pull/24799), thanks [@ndelangen](https://github.com/ndelangen)! - - Test Build: Disable docs related stuff for test builds - [#24691](https://github.com/storybookjs/storybook/pull/24691), thanks [@ndelangen](https://github.com/ndelangen)! - - Test Build: Disable telemetry for test builds - [#24706](https://github.com/storybookjs/storybook/pull/24706), thanks [@kasperpeulen](https://github.com/kasperpeulen)! - - Test Build: Disable warnOnIncompatibleAddons - [#24797](https://github.com/storybookjs/storybook/pull/24797), thanks [@ndelangen](https://github.com/ndelangen)! - - Test Build: Filter out addon-docs from essentials in the test build - [#24994](https://github.com/storybookjs/storybook/pull/24994), thanks [@kasperpeulen](https://github.com/kasperpeulen)! - - Test Build: Fix disabledAddons filter - [#24924](https://github.com/storybookjs/storybook/pull/24924), thanks [@IanVS](https://github.com/IanVS)! - - Test Build: Fix indexer bug - [#24890](https://github.com/storybookjs/storybook/pull/24890), thanks [@ndelangen](https://github.com/ndelangen)! - - Test Build: Globalize `@storybook/blocks` if `build.test.emptyBlocks` is `true` - [#24650](https://github.com/storybookjs/storybook/pull/24650), thanks [@ndelangen](https://github.com/ndelangen)! - - Test Build: Implement builder options for test build - [#24826](https://github.com/storybookjs/storybook/pull/24826), thanks [@kasperpeulen](https://github.com/kasperpeulen)! - - Test Build: Improve config loading & naming - [#24837](https://github.com/storybookjs/storybook/pull/24837), thanks [@ndelangen](https://github.com/ndelangen)! - - Test Build: No sourcemaps for test builds - [#24804](https://github.com/storybookjs/storybook/pull/24804), thanks [@ndelangen](https://github.com/ndelangen)! - - Test Build: Revert defaulting to SWC in test build, but keep using esbuild for minification - [#24843](https://github.com/storybookjs/storybook/pull/24843), thanks [@kasperpeulen](https://github.com/kasperpeulen)! - - Test: Create @storybook/test package based on vitest - [#24392](https://github.com/storybookjs/storybook/pull/24392), thanks [@kasperpeulen](https://github.com/kasperpeulen)! - - Test: Don\'t attach action to function mock if action was added already - [#24966](https://github.com/storybookjs/storybook/pull/24966), thanks [@tmeasday](https://github.com/tmeasday)! - - Test: Model loaders as before each and restore mocks properly - [#24948](https://github.com/storybookjs/storybook/pull/24948), thanks [@kasperpeulen](https://github.com/kasperpeulen)! - - Theming: Add theme variable to set the preview background color - [#24575](https://github.com/storybookjs/storybook/pull/24575), thanks [@JReinhold](https://github.com/JReinhold)! - - Typescript: Add \'skipCompiler\' option to TypeScript presets - [#24847](https://github.com/storybookjs/storybook/pull/24847), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - UI: Fix horizontal scroll bar in Canvas hidden by styling - [#24408](https://github.com/storybookjs/storybook/pull/24408), thanks [@yoshi2no](https://github.com/yoshi2no)! - - UI: improve A11Y remove redundant styling rules, update icon color - [#24402](https://github.com/storybookjs/storybook/pull/24402), thanks [@tolkadot](https://github.com/tolkadot)! - - UI: Logo fixed value - [#24726](https://github.com/storybookjs/storybook/pull/24726), thanks [@black-arm](https://github.com/black-arm)! - - UI: Update zIndex on NotificationList to fix the notification not being clickable in certain cases - [#24602](https://github.com/storybookjs/storybook/pull/24602), thanks [@yoshi2no](https://github.com/yoshi2no)! - - Viewport: Add newer device viewports - [#24777](https://github.com/storybookjs/storybook/pull/24777), thanks [@Tomo5524](https://github.com/Tomo5524)! - - Vite: Prevent non-deterministic build output - [#24833](https://github.com/storybookjs/storybook/pull/24833), thanks [@henkerik](https://github.com/henkerik)! - - Webpack: Add export-order-loader and remove babel-plugin-named-exports-order - [#24749](https://github.com/storybookjs/storybook/pull/24749), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Webpack: Add react-docgen loader and remove babel-plugin-react-docgen - [#24762](https://github.com/storybookjs/storybook/pull/24762), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Webpack: Fix race condition for export-order loader - [#24817](https://github.com/storybookjs/storybook/pull/24817), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Webpack: Hide critical dependency warning - [#24784](https://github.com/storybookjs/storybook/pull/24784), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Webpack: Only load babel config when babel-loader is used - [#25002](https://github.com/storybookjs/storybook/pull/25002), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - - Webpack: Resolve circular dependency and fix HMR - [#24974](https://github.com/storybookjs/storybook/pull/24974), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! - +- Actions: Attach spies on actions across stories when defined in meta - [#24451](https://github.com/storybookjs/storybook/pull/24451), thanks [@kasperpeulen](https://github.com/kasperpeulen)! +- Actions: Fix `@storybook/core-events/preview-errors` dependency missing for Yarn PnP - [#24973](https://github.com/storybookjs/storybook/pull/24973), thanks [@JReinhold](https://github.com/JReinhold)! +- Actions: Fix missing crypto module crashing React Native - [#24546](https://github.com/storybookjs/storybook/pull/24546), thanks [@dannyhw](https://github.com/dannyhw)! +- Actions: Warn on implicit actions - [#24856](https://github.com/storybookjs/storybook/pull/24856), thanks [@kasperpeulen](https://github.com/kasperpeulen)! +- Addon A11y: Avoid CSP issue - [#24477](https://github.com/storybookjs/storybook/pull/24477), thanks [@Marklb](https://github.com/Marklb)! +- Addon: Move Visual Test addon to the code directory - [#24771](https://github.com/storybookjs/storybook/pull/24771), thanks [@cdedreuille](https://github.com/cdedreuille)! +- Addons, core: Make `react` and Storybook packages `devDependencies` where possible - [#24676](https://github.com/storybookjs/storybook/pull/24676), thanks [@JReinhold](https://github.com/JReinhold)! +- Addons, core: Make `react` and Storybook packages `devDependencies` where possible - ATTEMPT 2 - [#24834](https://github.com/storybookjs/storybook/pull/24834), thanks [@JReinhold](https://github.com/JReinhold)! +- Angular: Add source-map option to builder - [#24466](https://github.com/storybookjs/storybook/pull/24466), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Angular: Handle nested module metadata - [#24798](https://github.com/storybookjs/storybook/pull/24798), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Angular: Include object configured styles - [#24768](https://github.com/storybookjs/storybook/pull/24768), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Babel: Update all @babel/\* dependencies - [#24610](https://github.com/storybookjs/storybook/pull/24610), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- CLI: Add "doctor" command - [#22236](https://github.com/storybookjs/storybook/pull/22236), thanks [@yannbf](https://github.com/yannbf)! +- CLI: Add @storybook/addon-designs to non-core list - [#24507](https://github.com/storybookjs/storybook/pull/24507), thanks [@yannbf](https://github.com/yannbf)! +- CLI: Ensure errors with opening the browser are caught - [#24668](https://github.com/storybookjs/storybook/pull/24668), thanks [@xueyawei](https://github.com/xueyawei)! +- CLI: Ignore `addon-onboarding` when checking versions - [#24634](https://github.com/storybookjs/storybook/pull/24634), thanks [@JReinhold](https://github.com/JReinhold)! +- CLI: Use @storybook/test in template stories - [#24393](https://github.com/storybookjs/storybook/pull/24393), thanks [@yannbf](https://github.com/yannbf)! +- Controls: Improve accessibility of BooleanControl for screen readers - [#24418](https://github.com/storybookjs/storybook/pull/24418), thanks [@danielmarcano](https://github.com/danielmarcano)! +- Core-Server: Ignore all node_module folders for watchpack - [#24553](https://github.com/storybookjs/storybook/pull/24553), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Core: Add deprecation notice for Vite + CommonJS - [#23950](https://github.com/storybookjs/storybook/pull/23950), thanks [@JReinhold](https://github.com/JReinhold)! +- Core: Detect no matching export error in storybook start and build - [#24877](https://github.com/storybookjs/storybook/pull/24877), thanks [@yannbf](https://github.com/yannbf)! +- Core: Fix `useStoryPrepared` hook failing with `undefined` data - [#22631](https://github.com/storybookjs/storybook/pull/22631), thanks [@SpookyJelly](https://github.com/SpookyJelly)! +- Core: Fix pnp support when cache dir is outside working dir - [#24572](https://github.com/storybookjs/storybook/pull/24572), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Core: Fix post message channel location.search access for React Native - [#24545](https://github.com/storybookjs/storybook/pull/24545), thanks [@dannyhw](https://github.com/dannyhw)! +- Core: Gracefully handle error when parsing preview.js file - [#24858](https://github.com/storybookjs/storybook/pull/24858), thanks [@yannbf](https://github.com/yannbf)! +- Core: Make warnOnIncompatibleAddons fault-tolerant - [#24880](https://github.com/storybookjs/storybook/pull/24880), thanks [@taozhou-glean](https://github.com/taozhou-glean)! +- Dependencies: Fix Yarn 4 failing to install due to jscodeshift dependency issue - [#24914](https://github.com/storybookjs/storybook/pull/24914), thanks [@samvv](https://github.com/samvv)! +- Dependencies: Update @babel/traverse and @babel/core to fix vulnerability - [#24670](https://github.com/storybookjs/storybook/pull/24670), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Dependencies: Update browserify-sign transitive dependency - [#24674](https://github.com/storybookjs/storybook/pull/24674), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Dependencies: Update jscodeshift to v0.15.1 - [#24882](https://github.com/storybookjs/storybook/pull/24882), thanks [@epreston](https://github.com/epreston)! +- Dependencies: Update nx dependencies to v17 - [#24671](https://github.com/storybookjs/storybook/pull/24671), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Doc Blocks: Add support for `of` prop to `Primary` block - [#23849](https://github.com/storybookjs/storybook/pull/23849), thanks [@Wilson2k](https://github.com/Wilson2k)! +- Doc Blocks: Remove `defaultProps` in `Stories` block - [#24506](https://github.com/storybookjs/storybook/pull/24506), thanks [@WouterK12](https://github.com/WouterK12)! +- Docs: Changes corresponding to docs design updates - [#24925](https://github.com/storybookjs/storybook/pull/24925), thanks [@kylegach](https://github.com/kylegach)! +- Maintenance: Split renderers preview entrypoints - [#24623](https://github.com/storybookjs/storybook/pull/24623), thanks [@ndelangen](https://github.com/ndelangen)! +- Manager: Update `store.settings.lastTrackedStoryId` - [#24115](https://github.com/storybookjs/storybook/pull/24115), thanks [@rashidshamloo](https://github.com/rashidshamloo)! +- ManagerAPI: Fix setting status without index, crashes storybook - [#24866](https://github.com/storybookjs/storybook/pull/24866), thanks [@ndelangen](https://github.com/ndelangen)! +- ManagerBuilder: Fix `"type": "commonjs"` compatibility - [#24534](https://github.com/storybookjs/storybook/pull/24534), thanks [@ndelangen](https://github.com/ndelangen)! +- Next.js: Add avif support - [#24611](https://github.com/storybookjs/storybook/pull/24611), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Next.js: Add back image context CommonJS export - [#24885](https://github.com/storybookjs/storybook/pull/24885), thanks [@martinnabhan](https://github.com/martinnabhan)! +- Next.js: Add experimental SWC support - [#24852](https://github.com/storybookjs/storybook/pull/24852), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Next.js: Fix Fast Refresh config for SWC mode - [#24991](https://github.com/storybookjs/storybook/pull/24991), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Next.js: Fix forwarding ref for Image component - [#24648](https://github.com/storybookjs/storybook/pull/24648), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Next.js: Fix import path in swc loader - [#24922](https://github.com/storybookjs/storybook/pull/24922), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Next.js: Fix react-docgen usage with preset-env settings - [#24993](https://github.com/storybookjs/storybook/pull/24993), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Next.js: Remove duplicate Fast Refresh plugin init - [#24963](https://github.com/storybookjs/storybook/pull/24963), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- React: Upgrade `react-docgen` to v7 - [#24530](https://github.com/storybookjs/storybook/pull/24530), thanks [@shilman](https://github.com/shilman)! +- ReactNative: Fix missing assert dep in docs-tools - [#24732](https://github.com/storybookjs/storybook/pull/24732), thanks [@dannyhw](https://github.com/dannyhw)! +- Svelte: Fix decorators always running twice - [#24921](https://github.com/storybookjs/storybook/pull/24921), thanks [@paoloricciuti](https://github.com/paoloricciuti)! +- Svelte: Fix source with decorators always showing the `SlotDecorator` component - [#24800](https://github.com/storybookjs/storybook/pull/24800), thanks [@JReinhold](https://github.com/JReinhold)! +- SvelteKit: Add experimental page and navigation mocking - [#24795](https://github.com/storybookjs/storybook/pull/24795), thanks [@paoloricciuti](https://github.com/paoloricciuti)! +- SvelteKit: Default to log an action for `goto`, `invalidate` and `invalidateAll` - [#24955](https://github.com/storybookjs/storybook/pull/24955), thanks [@paoloricciuti](https://github.com/paoloricciuti)! +- SWC: Add settings for react and preact - [#24805](https://github.com/storybookjs/storybook/pull/24805), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Test Build: Add env-variable support to `--test` CLI-flag - [#24862](https://github.com/storybookjs/storybook/pull/24862), thanks [@ndelangen](https://github.com/ndelangen)! +- Test Build: Add tests and rename to camelCase - [#24911](https://github.com/storybookjs/storybook/pull/24911), thanks [@ndelangen](https://github.com/ndelangen)! +- Test Build: Disable composition when `--test` is `true` - [#24799](https://github.com/storybookjs/storybook/pull/24799), thanks [@ndelangen](https://github.com/ndelangen)! +- Test Build: Disable docs related stuff for test builds - [#24691](https://github.com/storybookjs/storybook/pull/24691), thanks [@ndelangen](https://github.com/ndelangen)! +- Test Build: Disable telemetry for test builds - [#24706](https://github.com/storybookjs/storybook/pull/24706), thanks [@kasperpeulen](https://github.com/kasperpeulen)! +- Test Build: Disable warnOnIncompatibleAddons - [#24797](https://github.com/storybookjs/storybook/pull/24797), thanks [@ndelangen](https://github.com/ndelangen)! +- Test Build: Filter out addon-docs from essentials in the test build - [#24994](https://github.com/storybookjs/storybook/pull/24994), thanks [@kasperpeulen](https://github.com/kasperpeulen)! +- Test Build: Fix disabledAddons filter - [#24924](https://github.com/storybookjs/storybook/pull/24924), thanks [@IanVS](https://github.com/IanVS)! +- Test Build: Fix indexer bug - [#24890](https://github.com/storybookjs/storybook/pull/24890), thanks [@ndelangen](https://github.com/ndelangen)! +- Test Build: Globalize `@storybook/blocks` if `build.test.emptyBlocks` is `true` - [#24650](https://github.com/storybookjs/storybook/pull/24650), thanks [@ndelangen](https://github.com/ndelangen)! +- Test Build: Implement builder options for test build - [#24826](https://github.com/storybookjs/storybook/pull/24826), thanks [@kasperpeulen](https://github.com/kasperpeulen)! +- Test Build: Improve config loading & naming - [#24837](https://github.com/storybookjs/storybook/pull/24837), thanks [@ndelangen](https://github.com/ndelangen)! +- Test Build: No sourcemaps for test builds - [#24804](https://github.com/storybookjs/storybook/pull/24804), thanks [@ndelangen](https://github.com/ndelangen)! +- Test Build: Revert defaulting to SWC in test build, but keep using esbuild for minification - [#24843](https://github.com/storybookjs/storybook/pull/24843), thanks [@kasperpeulen](https://github.com/kasperpeulen)! +- Test: Create @storybook/test package based on vitest - [#24392](https://github.com/storybookjs/storybook/pull/24392), thanks [@kasperpeulen](https://github.com/kasperpeulen)! +- Test: Don\'t attach action to function mock if action was added already - [#24966](https://github.com/storybookjs/storybook/pull/24966), thanks [@tmeasday](https://github.com/tmeasday)! +- Test: Model loaders as before each and restore mocks properly - [#24948](https://github.com/storybookjs/storybook/pull/24948), thanks [@kasperpeulen](https://github.com/kasperpeulen)! +- Theming: Add theme variable to set the preview background color - [#24575](https://github.com/storybookjs/storybook/pull/24575), thanks [@JReinhold](https://github.com/JReinhold)! +- Typescript: Add \'skipCompiler\' option to TypeScript presets - [#24847](https://github.com/storybookjs/storybook/pull/24847), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- UI: Fix horizontal scroll bar in Canvas hidden by styling - [#24408](https://github.com/storybookjs/storybook/pull/24408), thanks [@yoshi2no](https://github.com/yoshi2no)! +- UI: improve A11Y remove redundant styling rules, update icon color - [#24402](https://github.com/storybookjs/storybook/pull/24402), thanks [@tolkadot](https://github.com/tolkadot)! +- UI: Logo fixed value - [#24726](https://github.com/storybookjs/storybook/pull/24726), thanks [@black-arm](https://github.com/black-arm)! +- UI: Update zIndex on NotificationList to fix the notification not being clickable in certain cases - [#24602](https://github.com/storybookjs/storybook/pull/24602), thanks [@yoshi2no](https://github.com/yoshi2no)! +- Viewport: Add newer device viewports - [#24777](https://github.com/storybookjs/storybook/pull/24777), thanks [@Tomo5524](https://github.com/Tomo5524)! +- Vite: Prevent non-deterministic build output - [#24833](https://github.com/storybookjs/storybook/pull/24833), thanks [@henkerik](https://github.com/henkerik)! +- Webpack: Add export-order-loader and remove babel-plugin-named-exports-order - [#24749](https://github.com/storybookjs/storybook/pull/24749), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Webpack: Add react-docgen loader and remove babel-plugin-react-docgen - [#24762](https://github.com/storybookjs/storybook/pull/24762), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Webpack: Fix race condition for export-order loader - [#24817](https://github.com/storybookjs/storybook/pull/24817), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Webpack: Hide critical dependency warning - [#24784](https://github.com/storybookjs/storybook/pull/24784), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Webpack: Only load babel config when babel-loader is used - [#25002](https://github.com/storybookjs/storybook/pull/25002), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Webpack: Resolve circular dependency and fix HMR - [#24974](https://github.com/storybookjs/storybook/pull/24974), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! + ## 7.5.2 diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 6e9e1eb4ba44..aec117765b12 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,50 @@ +## 8.0.0-rc.5 + +- CLI: Automigration fix version detection of upgrading related packages - [#26410](https://github.com/storybookjs/storybook/pull/26410), thanks @ndelangen! + +## 8.0.0-rc.4 + +- Actions: Fix attaching action after a spy is restored to original function - [#26364](https://github.com/storybookjs/storybook/pull/26364), thanks @kasperpeulen! +- CLI: Add explicit actions to header story - [#26352](https://github.com/storybookjs/storybook/pull/26352), thanks @kasperpeulen! +- CLI: Automigration for upgrading storybook related dependencies - [#26377](https://github.com/storybookjs/storybook/pull/26377), thanks @ndelangen! +- CLI: Fix doctor compatibility check - [#26363](https://github.com/storybookjs/storybook/pull/26363), thanks @yannbf! +- CLI: Fix fn reference in preact templates - [#26384](https://github.com/storybookjs/storybook/pull/26384), thanks @kasperpeulen! +- CLI: Remove duplicated dependency warning - [#26385](https://github.com/storybookjs/storybook/pull/26385), thanks @yannbf! +- CLI: Vite migration link (shorter) - [#26379](https://github.com/storybookjs/storybook/pull/26379), thanks @ndelangen! +- Composition: Fix refs not loading when there's multiple - [#26356](https://github.com/storybookjs/storybook/pull/26356), thanks @ndelangen! +- Dependencies: Broaden `esbuild` version range - [#26405](https://github.com/storybookjs/storybook/pull/26405), thanks @ndelangen! +- Maintenance: Replace `@storybook/testing-library` with `@storybook/test` in monorepo - [#26351](https://github.com/storybookjs/storybook/pull/26351), thanks @ndelangen! +- Maintenance: What's new modal changes - [#26355](https://github.com/storybookjs/storybook/pull/26355), thanks @kasperpeulen! +- Portable Stories: Fix injected root element changing layout - [#26387](https://github.com/storybookjs/storybook/pull/26387), thanks @JReinhold! +- React: Support all React component types in JSX Decorator - [#26382](https://github.com/storybookjs/storybook/pull/26382), thanks @yannbf! + +## 8.0.0-rc.3 + +- Addon-themes: Fix switcher initialization after first start - [#26353](https://github.com/storybookjs/storybook/pull/26353), thanks @valentinpalkovic! +- Build: Upgrade `esbuild` (`0.20.1`) - [#26255](https://github.com/storybookjs/storybook/pull/26255), thanks @43081j! +- Core: Fix path separator issue in check-addon-order - [#26362](https://github.com/storybookjs/storybook/pull/26362), thanks @valentinpalkovic! +- Dependencies: Remove `qs` from `@storybook/manager-api` & `@storybook/channels` - [#26285](https://github.com/storybookjs/storybook/pull/26285), thanks @43081j! +- UI: Fix sidebar scrolling to selected story when state changes - [#26337](https://github.com/storybookjs/storybook/pull/26337), thanks @JReinhold! +- UI: Remove 'left' property from TooltipLinkList and Link components - [#26324](https://github.com/storybookjs/storybook/pull/26324), thanks @valentinpalkovic! +- Viewport: Fix editing when default viewport is set - [#26360](https://github.com/storybookjs/storybook/pull/26360), thanks @shilman! +- Vue: Fix reference error when using re-exports with "vue-component-meta" - [#26303](https://github.com/storybookjs/storybook/pull/26303), thanks @larsrickert! + +## 8.0.0-rc.2 + +- CLI: Add @storybook/addons automigration - [#26295](https://github.com/storybookjs/storybook/pull/26295), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- CLI: Fix vite config automigration to resolve from project root - [#26262](https://github.com/storybookjs/storybook/pull/26262), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- CLI: Improve `add` command & add tests - [#26298](https://github.com/storybookjs/storybook/pull/26298), thanks [@ndelangen](https://github.com/ndelangen)! +- CLI: Update minimum Node.js version requirement - [#26312](https://github.com/storybookjs/storybook/pull/26312), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- CSF-tools/Codemods: Upgrade recast - [#26286](https://github.com/storybookjs/storybook/pull/26286), thanks [@43081j](https://github.com/43081j)! +- Controls: Fix type summary when table.type unset - [#26283](https://github.com/storybookjs/storybook/pull/26283), thanks [@shilman](https://github.com/shilman)! +- Core: Add event when serverChannel disconnects - [#26322](https://github.com/storybookjs/storybook/pull/26322), thanks [@ndelangen](https://github.com/ndelangen)! +- Core: Fix composition of storybooks on same origin - [#26304](https://github.com/storybookjs/storybook/pull/26304), thanks [@ndelangen](https://github.com/ndelangen)! +- Portable stories: Improve existing APIs, add loaders support - [#26267](https://github.com/storybookjs/storybook/pull/26267), thanks [@yannbf](https://github.com/yannbf)! +- React: Handle TypeScript path aliases in react-docgen loader - [#26273](https://github.com/storybookjs/storybook/pull/26273), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Svelte: Support `5.0.0-next.65` prerelease - [#26188](https://github.com/storybookjs/storybook/pull/26188), thanks [@JReinhold](https://github.com/JReinhold)! +- Upgrade: Add missing isUpgrade parameter to automigrate function - [#26293](https://github.com/storybookjs/storybook/pull/26293), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Vue: Return component from `composeStory` - [#26317](https://github.com/storybookjs/storybook/pull/26317), thanks [@JReinhold](https://github.com/JReinhold)! + ## 8.0.0-rc.1 - CLI: Fix addon compatibility check error reporting in storybook dev - [#26258](https://github.com/storybookjs/storybook/pull/26258), thanks [@yannbf](https://github.com/yannbf)! diff --git a/MIGRATION.md b/MIGRATION.md index 5919ec501c42..561a9a61b899 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -4,7 +4,7 @@ - [Portable stories](#portable-stories) - [Project annotations are now merged instead of overwritten in composeStory](#project-annotations-are-now-merged-instead-of-overwritten-in-composestory) - [Type change in `composeStories` API](#type-change-in-composestories-api) - - [DOM structure changed in portable stories](#dom-structure-changed-in-portable-stories) + - [Composed Vue stories are now components instead of functions](#composed-vue-stories-are-now-components-instead-of-functions) - [Tab addons are now routed to a query parameter](#tab-addons-are-now-routed-to-a-query-parameter) - [Default keyboard shortcuts changed](#default-keyboard-shortcuts-changed) - [Manager addons are now rendered with React 18](#manager-addons-are-now-rendered-with-react-18) @@ -64,6 +64,7 @@ - [Removed `passArgsFirst` option](#removed-passargsfirst-option) - [Methods and properties from AddonStore](#methods-and-properties-from-addonstore) - [Methods and properties from PreviewAPI](#methods-and-properties-from-previewapi) + - [Removals in @storybook/components](#removals-in-storybookcomponents) - [Removals in @storybook/types](#removals-in-storybooktypes) - [--use-npm flag in storybook CLI](#--use-npm-flag-in-storybook-cli) - [hideNoControlsWarning parameter from addon controls](#hidenocontrolswarning-parameter-from-addon-controls) @@ -438,32 +439,30 @@ await Primary.play!(...) // if you want a runtime error when the play function d There are plans to make the type of the play function be inferred based on your imported story's play function in a near future, so the types will be 100% accurate. -#### DOM structure changed in portable stories +#### Composed Vue stories are now components instead of functions -The portable stories API now adds a wrapper to your stories with a unique id based on your story id, such as: +`composeStory` (and `composeStories`) from `@storybook/vue3` now return Vue components rather than story functions that return components. This means that when rendering these composed stories you just pass the composed story _without_ first calling it. -```html -
- -
-``` - -This means that if you take DOM snapshots of your stories, they will be affected and you will have to update them. +Previously when using `composeStory` from `@storybook/testing-vue3`, you would render composed stories with e.g. `render(MyStoryComposedStory({ someProp: true}))`. That is now changed to more [closely match how you would render regular Vue components](https://testing-library.com/docs/vue-testing-library/examples). -The id calculation is based on different heuristics based on your Meta title and Story name. When using `composeStories`, the id can be inferred automatically. However, when using `composeStory` and your story does not explicitly have a `storyName` property, the story name can't be inferred automatically. As a result, its name will be "Unnamed Story", resulting in a wrapper id like `"#storybook-story-button--unnamed-story"`. If the id matters to you and you want to fix it, you have to specify the `exportsName` property like so: +When migrating from `@storybook/testing-vue3`, you will likely hit the following error: ```ts -test("snapshots the story with custom id", () => { - const Primary = composeStory( - stories.Primary, - stories.default, - undefined, - // If you do not want the `unnamed-story` id, you have to pass the name of the story as a parameter - "Primary" - ); - - const { baseElement } = render(); - expect(baseElement).toMatchSnapshot(); +TypeError: Cannot read properties of undefined (reading 'devtoolsRawSetupState') +``` + +To fix it, you should change the usage of the composed story to reference it instead of calling it as a function. Here's an example using `@testing-library/vue` and Vitest: + +```diff +import { it } from 'vitest'; +import { render } from '@testing-library/vue'; +import * as stories from './Button.stories'; +import { composeStory } from '@storybook/vue3'; + +it('renders primary button', () => { + const Primary = composeStory(stories.Primary, stories.default); +- render(Primary({ label: 'Hello world' })); ++ render(Primary, { props: { label: 'Hello world' } }); }); ``` @@ -1040,6 +1039,45 @@ The following exports from `@storybook/preview-api` are now removed: Please file an issue if you need these APIs. +#### Removals in @storybook/components + +The `TooltipLinkList` UI component used to customize the Storybook toolbar has been updated to use the `icon` property instead of the `left` property to position its content. If you've enabled this property in your `globalTypes` configuration, addons, or any other place, you'll need to replace it with an `icon` property to mimic the same behavior. For example: + +```diff +// .storybook/preview.js|ts +// Replace your-framework with the framework you are using (e.g., react, vue3) +import { Preview } from '@storybook/your-framework'; + +const preview: Preview = { + globalTypes: { + locale: { + description: 'Internationalization locale', + defaultValue: 'en', + toolbar: { + icon: 'globe', + items: [ + { + value: 'en', + right: 'πŸ‡ΊπŸ‡Έ', +- left: 'οΌ„' ++ icon: 'facehappy' + title: 'English' + }, + { value: 'fr', right: 'πŸ‡«πŸ‡·', title: 'FranΓ§ais' }, + { value: 'es', right: 'πŸ‡ͺπŸ‡Έ', title: 'EspaΓ±ol' }, + { value: 'zh', right: 'πŸ‡¨πŸ‡³', title: 'δΈ­ζ–‡' }, + { value: 'kr', right: 'πŸ‡°πŸ‡·', title: 'ν•œκ΅­μ–΄' }, + ], + }, + }, + }, +}; + +export default preview; +``` + +To learn more about the available icons and their names, see the [Storybook documentation](https://storybook.js.org/docs/8.0/faq#what-icons-are-available-for-my-toolbar-or-my-addon). + #### Removals in @storybook/types The following exports from `@storybook/types` are now removed: @@ -1061,7 +1099,7 @@ The `hideNoControlsWarning` parameter is now removed. [More info here](#addon-co The `setGlobalConfig` (used for reusing stories in your tests) is now removed in favor of `setProjectAnnotations`. ```ts -import { setProjectAnnotations } from `@storybook/testing-react`. +import { setProjectAnnotations } from `@storybook/react`. ``` #### StorybookViteConfig type from @storybook/builder-vite diff --git a/code/addons/a11y/package.json b/code/addons/a11y/package.json index ebae68256c87..73f802106851 100644 --- a/code/addons/a11y/package.json +++ b/code/addons/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-a11y", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Test component compliance with web accessibility standards", "keywords": [ "a11y", diff --git a/code/addons/actions/package.json b/code/addons/actions/package.json index bc89e2f5e5c7..2f13f8e98a15 100644 --- a/code/addons/actions/package.json +++ b/code/addons/actions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-actions", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Get UI feedback when an action is performed on an interactive element", "keywords": [ "storybook", diff --git a/code/addons/actions/src/loaders.ts b/code/addons/actions/src/loaders.ts index cc6c8494b7fc..3acfa9795eef 100644 --- a/code/addons/actions/src/loaders.ts +++ b/code/addons/actions/src/loaders.ts @@ -2,6 +2,8 @@ import type { LoaderFunction } from '@storybook/types'; import { action } from './runtime'; +export const tinySpyInternalState = Symbol.for('tinyspy:spy'); + const attachActionsToFunctionMocks: LoaderFunction = (context) => { const { args, @@ -15,7 +17,11 @@ const attachActionsToFunctionMocks: LoaderFunction = (context) => { typeof value === 'function' && '_isMockFunction' in value && value._isMockFunction ) .forEach(([key, value]) => { - const previous = value.getMockImplementation(); + // See this discussion for context: + // https://github.com/vitest-dev/vitest/pull/5352 + const previous = + value.getMockImplementation() ?? + (tinySpyInternalState in value ? value[tinySpyInternalState]?.getOriginal() : undefined); if (previous?._actionAttached !== true && previous?.isAction !== true) { const implementation = (...params: unknown[]) => { action(key)(...params); diff --git a/code/addons/backgrounds/package.json b/code/addons/backgrounds/package.json index 4fbdfc5c027c..08ea5529d139 100644 --- a/code/addons/backgrounds/package.json +++ b/code/addons/backgrounds/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-backgrounds", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Switch backgrounds to view components in different settings", "keywords": [ "addon", diff --git a/code/addons/controls/package.json b/code/addons/controls/package.json index 588947d02c70..d59821722737 100644 --- a/code/addons/controls/package.json +++ b/code/addons/controls/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-controls", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Interact with component inputs dynamically in the Storybook UI", "keywords": [ "addon", diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json index a159c03063b6..3c6eaa0d625a 100644 --- a/code/addons/docs/package.json +++ b/code/addons/docs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-docs", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Document component usage and properties in Markdown", "keywords": [ "addon", diff --git a/code/addons/docs/template/stories/docspage/autoplay.stories.ts b/code/addons/docs/template/stories/docspage/autoplay.stories.ts index 36fc395949d8..6ebdc43b3ad7 100644 --- a/code/addons/docs/template/stories/docspage/autoplay.stories.ts +++ b/code/addons/docs/template/stories/docspage/autoplay.stories.ts @@ -1,6 +1,5 @@ import { global as globalThis } from '@storybook/global'; -import { expect } from '@storybook/test'; -import { within } from '@storybook/testing-library'; +import { expect, within } from '@storybook/test'; export default { component: globalThis.Components.Pre, diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index 61dbf507bbb1..3b9143837d78 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-essentials", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Curated addons to bring out the best of Storybook", "keywords": [ "addon", diff --git a/code/addons/gfm/package.json b/code/addons/gfm/package.json index 5491ba3a58e4..b35c226de305 100644 --- a/code/addons/gfm/package.json +++ b/code/addons/gfm/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-mdx-gfm", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "GitHub Flavored Markdown in Storybook", "keywords": [ "addon", diff --git a/code/addons/highlight/package.json b/code/addons/highlight/package.json index 28f331bf4dc8..bae6a544048d 100644 --- a/code/addons/highlight/package.json +++ b/code/addons/highlight/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-highlight", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Highlight DOM nodes within your stories", "keywords": [ "storybook-addons", diff --git a/code/addons/interactions/package.json b/code/addons/interactions/package.json index daece4716036..dbb88085b22b 100644 --- a/code/addons/interactions/package.json +++ b/code/addons/interactions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-interactions", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Automate, test and debug user interactions", "keywords": [ "storybook-addons", @@ -65,7 +65,7 @@ "@storybook/instrumenter": "workspace:*", "@storybook/manager-api": "workspace:*", "@storybook/preview-api": "workspace:*", - "@storybook/testing-library": "next", + "@storybook/test": "workspace:*", "@storybook/theming": "workspace:*", "@types/node": "^18.0.0", "formik": "^2.2.9", diff --git a/code/addons/interactions/src/components/Interaction.stories.tsx b/code/addons/interactions/src/components/Interaction.stories.tsx index b18cd7136c6a..a6f8bd3a3b46 100644 --- a/code/addons/interactions/src/components/Interaction.stories.tsx +++ b/code/addons/interactions/src/components/Interaction.stories.tsx @@ -1,7 +1,6 @@ import type { StoryObj, Meta } from '@storybook/react'; -import { expect } from '@storybook/test'; import { CallStates } from '@storybook/instrumenter'; -import { userEvent, within } from '@storybook/testing-library'; +import { userEvent, within, expect } from '@storybook/test'; import { getCalls } from '../mocks'; import { Interaction } from './Interaction'; diff --git a/code/addons/interactions/src/components/InteractionsPanel.stories.tsx b/code/addons/interactions/src/components/InteractionsPanel.stories.tsx index a2435113ef02..89f7ef115b59 100644 --- a/code/addons/interactions/src/components/InteractionsPanel.stories.tsx +++ b/code/addons/interactions/src/components/InteractionsPanel.stories.tsx @@ -2,8 +2,7 @@ import React from 'react'; import type { StoryObj, Meta } from '@storybook/react'; import { CallStates } from '@storybook/instrumenter'; import { styled } from '@storybook/theming'; -import { userEvent, within, waitFor } from '@storybook/testing-library'; -import { expect } from '@storybook/test'; +import { userEvent, within, waitFor, expect } from '@storybook/test'; import isChromatic from 'chromatic/isChromatic'; import { getCalls, getInteractions } from '../mocks'; diff --git a/code/addons/jest/package.json b/code/addons/jest/package.json index 3b194968932c..bd511b8e0b5c 100644 --- a/code/addons/jest/package.json +++ b/code/addons/jest/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-jest", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "React storybook addon that show component jest report", "keywords": [ "addon", diff --git a/code/addons/links/package.json b/code/addons/links/package.json index d1777a65aa4d..686c684f829b 100644 --- a/code/addons/links/package.json +++ b/code/addons/links/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-links", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Link stories together to build demos and prototypes with your UI components", "keywords": [ "addon", diff --git a/code/addons/measure/package.json b/code/addons/measure/package.json index 2d23660bdac2..1985366f670c 100644 --- a/code/addons/measure/package.json +++ b/code/addons/measure/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-measure", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Inspect layouts by visualizing the box model", "keywords": [ "storybook-addons", diff --git a/code/addons/onboarding/package.json b/code/addons/onboarding/package.json index 057221c7df4e..72726fb3726f 100644 --- a/code/addons/onboarding/package.json +++ b/code/addons/onboarding/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-onboarding", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook Addon Onboarding - Introduces a new onboarding experience", "keywords": [ "storybook-addons", @@ -55,7 +55,6 @@ "@storybook/react": "workspace:*", "@storybook/telemetry": "workspace:*", "@storybook/test": "workspace:*", - "@storybook/testing-library": "next", "@storybook/theming": "workspace:*", "@storybook/types": "workspace:*", "framer-motion": "^11.0.3", diff --git a/code/addons/onboarding/src/components/List/List.stories.tsx b/code/addons/onboarding/src/components/List/List.stories.tsx index 380fd07ca4cc..9ff667586fc3 100644 --- a/code/addons/onboarding/src/components/List/List.stories.tsx +++ b/code/addons/onboarding/src/components/List/List.stories.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; -import { userEvent, waitFor, within } from '@storybook/testing-library'; -import { expect } from '@storybook/test'; +import { userEvent, waitFor, within, expect } from '@storybook/test'; import { List } from './List'; import { ListItem } from './ListItem/ListItem'; diff --git a/code/addons/onboarding/src/components/Modal/Modal.stories.tsx b/code/addons/onboarding/src/components/Modal/Modal.stories.tsx index 51d19c49b4f5..527aa87d4323 100644 --- a/code/addons/onboarding/src/components/Modal/Modal.stories.tsx +++ b/code/addons/onboarding/src/components/Modal/Modal.stories.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; -import { userEvent, within } from '@storybook/testing-library'; -import { expect } from '@storybook/test'; +import { userEvent, within, expect } from '@storybook/test'; import { Modal } from './Modal'; diff --git a/code/addons/onboarding/src/components/PulsatingEffect/PulsatingEffect.stories.tsx b/code/addons/onboarding/src/components/PulsatingEffect/PulsatingEffect.stories.tsx index 67b31843dc45..6a87a2147c0a 100644 --- a/code/addons/onboarding/src/components/PulsatingEffect/PulsatingEffect.stories.tsx +++ b/code/addons/onboarding/src/components/PulsatingEffect/PulsatingEffect.stories.tsx @@ -1,8 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { PulsatingEffect } from './PulsatingEffect'; import React from 'react'; -import { within } from '@storybook/testing-library'; -import { expect } from '@storybook/test'; +import { within, expect } from '@storybook/test'; const meta: Meta = { component: PulsatingEffect, diff --git a/code/addons/onboarding/src/features/WriteStoriesModal/WriteStoriesModal.stories.tsx b/code/addons/onboarding/src/features/WriteStoriesModal/WriteStoriesModal.stories.tsx index d2284dbd913f..d2fe6ba470b4 100644 --- a/code/addons/onboarding/src/features/WriteStoriesModal/WriteStoriesModal.stories.tsx +++ b/code/addons/onboarding/src/features/WriteStoriesModal/WriteStoriesModal.stories.tsx @@ -1,8 +1,7 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; -import { waitFor, within } from '@storybook/testing-library'; -import { expect, fn } from '@storybook/test'; +import { waitFor, within, expect, fn } from '@storybook/test'; import { STORY_INDEX_INVALIDATED, STORY_RENDERED } from '@storybook/core-events'; import { WriteStoriesModal } from './WriteStoriesModal'; import typescriptSnippet from './code/typescript'; diff --git a/code/addons/outline/package.json b/code/addons/outline/package.json index a0a1ff61ab03..f8db2b4ea149 100644 --- a/code/addons/outline/package.json +++ b/code/addons/outline/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-outline", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Outline all elements with CSS to help with layout placement and alignment", "keywords": [ "storybook-addons", diff --git a/code/addons/storysource/package.json b/code/addons/storysource/package.json index c9fc3680601d..c7326a263ad1 100644 --- a/code/addons/storysource/package.json +++ b/code/addons/storysource/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-storysource", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "View a story’s source code to see how it works and paste into your app", "keywords": [ "addon", diff --git a/code/addons/themes/package.json b/code/addons/themes/package.json index c28b3426d2e1..a3599f8288e2 100644 --- a/code/addons/themes/package.json +++ b/code/addons/themes/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-themes", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Switch between multiple themes for you components in Storybook", "keywords": [ "css", diff --git a/code/addons/themes/src/constants.ts b/code/addons/themes/src/constants.ts index a1872de9e88e..677c9499a7b4 100644 --- a/code/addons/themes/src/constants.ts +++ b/code/addons/themes/src/constants.ts @@ -1,5 +1,5 @@ export const PARAM_KEY = 'themes' as const; -export const ADDON_ID = `storybook/${PARAM_KEY}}` as const; +export const ADDON_ID = `storybook/${PARAM_KEY}` as const; export const GLOBAL_KEY = 'theme' as const; export const THEME_SWITCHER_ID = `${ADDON_ID}/theme-switcher` as const; diff --git a/code/addons/themes/src/theme-switcher.tsx b/code/addons/themes/src/theme-switcher.tsx index ef27f0d25769..0a0ac5f34bec 100644 --- a/code/addons/themes/src/theme-switcher.tsx +++ b/code/addons/themes/src/theme-switcher.tsx @@ -1,5 +1,11 @@ import React, { Fragment, useMemo } from 'react'; -import { useAddonState, useChannel, useGlobals, useParameter } from '@storybook/manager-api'; +import { + useAddonState, + useChannel, + useGlobals, + useParameter, + addons, +} from '@storybook/manager-api'; import { styled } from '@storybook/theming'; import { IconButton, WithTooltip, TooltipLinkList } from '@storybook/components'; @@ -20,16 +26,23 @@ const IconButtonLabel = styled.div(({ theme }) => ({ const hasMultipleThemes = (themesList: ThemeAddonState['themesList']) => themesList.length > 1; const hasTwoThemes = (themesList: ThemeAddonState['themesList']) => themesList.length === 2; -export const ThemeSwitcher = () => { +export const ThemeSwitcher = React.memo(function ThemeSwitcher() { const { themeOverride } = useParameter( PARAM_KEY, DEFAULT_THEME_PARAMETERS ) as ThemeParameters; const [{ theme: selected }, updateGlobals] = useGlobals(); + const channel = addons.getChannel(); + const fromLast = channel.last(THEMING_EVENTS.REGISTER_THEMES); + const initializeThemeState = Object.assign({}, DEFAULT_ADDON_STATE, { + themesList: fromLast?.[0]?.themes || [], + themeDefault: fromLast?.[0]?.defaultTheme || '', + }); + const [{ themesList, themeDefault }, updateState] = useAddonState( THEME_SWITCHER_ID, - DEFAULT_ADDON_STATE + initializeThemeState ); useChannel({ @@ -103,4 +116,4 @@ export const ThemeSwitcher = () => { } return null; -}; +}); diff --git a/code/addons/toolbars/package.json b/code/addons/toolbars/package.json index f9aa8edb217e..ec4a11506175 100644 --- a/code/addons/toolbars/package.json +++ b/code/addons/toolbars/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-toolbars", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Create your own toolbar items that control story rendering", "keywords": [ "addon", diff --git a/code/addons/toolbars/src/types.ts b/code/addons/toolbars/src/types.ts index e07d11f73ae0..8427c53e4606 100644 --- a/code/addons/toolbars/src/types.ts +++ b/code/addons/toolbars/src/types.ts @@ -15,7 +15,6 @@ export type ToolbarShortcuts = Record { registerShortcuts(api, globals, updateGlobals, Object.keys(viewports)); - }, [viewports, globals.viewport]); + }, [viewports, globals, globals.viewport, updateGlobals, api]); useEffect(() => { const defaultRotated = defaultOrientation === 'landscape'; @@ -150,7 +150,18 @@ export const ViewportTool: FC = memo( viewportRotated: defaultRotated, }); } - }, [defaultOrientation, defaultViewport, globals, updateGlobals]); + // NOTE: we don't want to re-run this effect when `globals` changes + // due to https://github.com/storybookjs/storybook/issues/26334 + // + // Also, this *will* rerun every time you change story as the parameter is briefly `undefined`. + // This behaviour is intentional, if a bit of a happy accident in implementation. + // + // Ultimately this process of "locking in" a parameter value should be + // replaced by https://github.com/storybookjs/storybook/discussions/23347 + // or something similar. + // + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [defaultOrientation, defaultViewport, updateGlobals]); const item = list.find((i) => i.id === globals.viewport) || diff --git a/code/builders/builder-manager/package.json b/code/builders/builder-manager/package.json index e892d2dbf0e9..a31a2e91a374 100644 --- a/code/builders/builder-manager/package.json +++ b/code/builders/builder-manager/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-manager", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook manager builder", "keywords": [ "storybook" @@ -52,7 +52,7 @@ "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10", "browser-assert": "^1.2.1", "ejs": "^3.1.8", - "esbuild": "^0.18.0", + "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0", "esbuild-plugin-alias": "^0.2.1", "express": "^4.17.3", "fs-extra": "^11.1.0", diff --git a/code/builders/builder-vite/package.json b/code/builders/builder-vite/package.json index b1dd337500cb..1d0dd1b7610c 100644 --- a/code/builders/builder-vite/package.json +++ b/code/builders/builder-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-vite", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "A plugin to run and build Storybooks with Vite", "homepage": "https://github.com/storybookjs/storybook/tree/next/code/builders/builder-vite/#readme", "bugs": { diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index 09e2717e316a..31d47498ee40 100644 --- a/code/builders/builder-webpack5/package.json +++ b/code/builders/builder-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-webpack5", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/e2e-tests/addon-viewport.spec.ts b/code/e2e-tests/addon-viewport.spec.ts index ec96a3bd93ea..64f8eb097dfb 100644 --- a/code/e2e-tests/addon-viewport.spec.ts +++ b/code/e2e-tests/addon-viewport.spec.ts @@ -40,4 +40,26 @@ test.describe('addon-viewport', () => { // Compare the two widths await expect(adjustedDimensions?.width).not.toBe(originalDimensions?.width); }); + + test('viewport should be editable when a default viewport is set', async ({ page }) => { + const sbPage = new SbPage(page); + + // Story parameters/selected is set to small mobile + await sbPage.navigateToStory('addons/viewport/parameters', 'selected'); + + // Measure the original dimensions of previewRoot + const originalDimensions = await sbPage.getCanvasBodyElement().boundingBox(); + await expect(originalDimensions?.width).toBeDefined(); + + // Manually select "large mobile" and give it time to adjust + await sbPage.selectToolbar('[title="Change the size of the preview"]', '#list-item-mobile2'); + await new Promise((r) => setTimeout(r, 200)); + + // Measure the adjusted dimensions of previewRoot after clicking the mobile item. + const adjustedDimensions = await sbPage.getCanvasBodyElement().boundingBox(); + await expect(adjustedDimensions?.width).toBeDefined(); + + // Compare the two widths + await expect(adjustedDimensions?.width).not.toBe(originalDimensions?.width); + }); }); diff --git a/code/frameworks/angular/README.md b/code/frameworks/angular/README.md index d97e1ab93f2a..4bfadb7f16f4 100644 --- a/code/frameworks/angular/README.md +++ b/code/frameworks/angular/README.md @@ -1,324 +1,3 @@ # Storybook for Angular -- [Storybook for Angular](#storybook-for-angular) - - [Getting Started](#getting-started) - - [Setup Storybook for your Angular projects](#setup-storybook-for-your-angular-projects) - - [Run Storybook](#run-storybook) - - [Setup Compodoc](#setup-compodoc) - - [Automatic setup](#automatic-setup) - - [Manual setup](#manual-setup) - - [moduleMetadata decorator](#modulemetadata-decorator) - - [applicationConfig decorator](#applicationconfig-decorator) - - [FAQ](#faq) - - [How do I migrate to an Angular Storybook builder?](#how-do-i-migrate-to-an-angular-storybook-builder) - - [Do you have only one Angular project in your workspace?](#do-you-have-only-one-angular-project-in-your-workspace) - - [Adjust your `package.json`](#adjust-your-packagejson) - - [I have multiple projects in my Angular workspace](#i-have-multiple-projects-in-my-angular-workspace) - -Storybook for Angular is a UI development environment for your Angular components. -With it, you can visualize different states of your UI components and develop them interactively. - -![Storybook Screenshot](https://github.com/storybookjs/storybook/blob/main/media/storybook-intro.gif) - -Storybook runs outside of your app. -So you can develop UI components in isolation without worrying about app specific dependencies and requirements. - -## Getting Started - -```sh -cd my-angular-app -npx storybook@latest init -``` - -## Setup Storybook for your Angular projects - -Storybook supports Angular multi-project workspace. You can setup Storybook for each project in the workspace. When running `npx storybook@latest init` you will be asked for which project Storybook should be set up. Essentially, during initialization, the `.storybook` folder will be created and the `angular.json` will be edited to add the Storybook configuration for the selected project. The configuration looks approximately like this: - -```json -// angular.json -{ - ... - "projects": { - ... - "your-project": { - ... - "architect": { - ... - "storybook": { - "builder": "@storybook/angular:start-storybook", - "options": { - // the path to the storybook config directory - "configDir": ".storybook", - // the build target of your project - "browserTarget": "your-project:build", - // the port you want to start Storybook on - "port": 6006 - // further options are available and can be found in - // https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular/src/builders/start-storybook/schema.json - } - }, - "build-storybook": { - "builder": "@storybook/angular:build-storybook", - "options": { - "configDir": ".storybook", - "browserTarget": "your-project:build", - "outputDir": "dist/storybook/your-project" - // further options are available and can be found in - // https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular/src/builders/build-storybook/schema.json - } - } - } - } - } -} -``` - -## Run Storybook - -To run Storybook for a particular project, please run: - -```sh -ng run :storybook -``` - -To build Storybook, run: - -```sh -ng run :build-storybook -``` - -You will find the output in `dist/storybook/your-project`. - -For more information visit: [storybook.js.org](https://storybook.js.org) - -## Setup Compodoc - -You can include JSDoc comments above components, directives, and other parts of your Angular code to include documentation for those elements. Compodoc uses these comments to generate documentation for your application. In Storybook, it is useful to add explanatory comments above @Inputs and @Outputs, since these are the main elements that Storybook displays in its user interface. The @Inputs and @Outputs are the elements that you can interact with in Storybook, such as controls. - -### Automatic setup - -When installing Storybook via `sb init`, you will be given the option to set up Compodoc automatically. - -### Manual setup - -If you have already installed Storybook, you can set up Compodoc manually. - -Install the following dependencies: - -```sh -npm i -D @compodoc/compodoc -``` - -Add the following option to your to the Storybook Builder: - -```json -{ - ... - "projects": { - ... - "your-project": { - ... - "architect": { - ... - "storybook": { - "builder": "@storybook/angular:start-storybook", - "options": { - ... - "compodoc": true, - "compodocArgs": [ - "-e", - "json", - "-d", - // Where to store the generated documentation. It's usually the root of your Angular project. It's not necessarily the root of your Angular Workspace! - "." - ], - } - }, - "build-storybook": { - "builder": "@storybook/angular:build-storybook", - "options": { - ... - "compodoc": true, - "compodocArgs": [ - "-e", - "json", - "-d", - "." - ], - } - } - } - } - } -} -``` - -Go to your `.storybook/preview.js` and add the following: - -```js -import { setCompodocJson } from '@storybook/addon-docs/angular'; -import docJson from '../documentation.json'; -setCompodocJson(docJson); - -const preview: Preview = { - ... -}; - -export default preview; -``` - -## moduleMetadata decorator - -If your component has dependencies on other Angular directives and modules, these can be supplied using the moduleMetadata decorator either for all stories or for individual stories. - -```js -import { StoryFn, Meta, moduleMetadata } from '@storybook/angular'; -import { SomeComponent } from './some.component'; - -export default { - component: SomeComponent, - decorators: [ - // Apply metadata to all stories - moduleMetadata({ - // import necessary ngModules or standalone components - imports: [...], - // declare components that are used in the template - declarations: [...], - // List of providers that should be available to the root component and all its children. - providers: [...], - }), - ], -} as Meta; - -const Template = (): StoryFn => (args) => ({ - props: args, -}); - -export const Base = Template(); - -export const WithCustomProvider = Template(); -WithCustomProvider.decorators = [ - // Apply metadata to a specific story - moduleMetadata({ - imports: [...], - declarations: [...], - providers: [...] - }), -]; -``` - -## applicationConfig decorator - -If your component relies on application-wide providers, like the ones defined by BrowserAnimationsModule or any other modules which use the forRoot pattern to provide a ModuleWithProviders, you can use the applicationConfig decorator on the meta default export to provide them to the [bootstrapApplication function](https://angular.io/guide/standalone-components#configuring-dependency-injection), which we use to bootstrap the component in Storybook. - -```js - -import { StoryObj, Meta, applicationConfig } from '@storybook/angular'; -import { BrowserAnimationsModule, provideAnimations } from '@angular/platform-browser/animations'; -import { importProvidersFrom } from '@angular/core'; -import { ChipsModule } from './angular-src/chips.module'; - -const meta: Meta = { - component: ChipsGroupComponent, - decorators: [ - // Apply application config to all stories - applicationConfig({ - // List of providers and environment providers that should be available to the root component and all its children. - providers: [ - ... - // Import application-wide providers from a module - importProvidersFrom(BrowserAnimationsModule) - // Or use provide-style functions if available instead, e.g. - provideAnimations() - ], - }), - ], -}; - -export default meta; - -type Story = StoryObj; - -export const WithCustomApplicationProvider: Story = { - render: () => ({ - // Apply application config to a specific story - applicationConfig: { - // The providers will be merged with the ones defined in the applicationConfig decorators providers array of the global meta object - providers: [...] - } - }) -} -``` - -## FAQ - -### How do I migrate to an Angular Storybook builder? - -The Storybook [Angular builder](https://angular.io/guide/glossary#builder) is a new way to run Storybook in an Angular workspace. It is a drop-in replacement for running `storybook dev` and `storybook build` directly. - -You can run `npx storybook@next automigrate` to try let Storybook detect and automatically fix your configuration. Otherwise, you can follow the next steps to manually adjust your configuration. - -#### Do you have only one Angular project in your workspace? - -In this case go to your `angular.json` and add `storybook` and `build-storybook` entries in `architect` section of your project like shown above. - -##### Adjust your `package.json` - -Go to your `package.json` and adjust your script section. Usually, it will look like this: - -```json -{ - "scripts": { - "storybook": "start-storybook -p 6006", // or `storybook dev -p 6006` - "build-storybook": "build-storybook" // or `storybook build` - } -} -``` - -Now, you can run Storybook with `ng run :storybook` and build it with `ng run :build-storybook`. Adjust the scripts in your `package.json` accordingly. - -```json -{ - "scripts": { - "storybook": "ng run :storybook", // or `storybook dev -p 6006` - "build-storybook": "ng run :build-storybook" // or `storybook build` - } -} -``` - -Also remove the compodoc part in your script section if you have set it up previously. -It is now built-in in `@storybook/angular` and you don't have to call it explicitly: - -```json -{ - "scripts": { - "docs:json": "compodoc -p tsconfig.json -e json -d ./documentation", - "storybook": "npm run docs:json && start-storybook -p 6006", - "build-storybook": "npm run docs:json && build-storybook" - } -} -``` - -Change it to: - -```json -{ - "scripts": { - "storybook": "ng run :storybook", - "build-storybook": "ng run :build-storybook" - } -} -``` - -#### I have multiple projects in my Angular workspace - -In this case you have to adjust your `angular.json` and `package.json` as described above for each project in which you want to use Storybook. Please note, that each project should have a dedicated `.storybook` folder, which should be placed in the root of the project. - -You can run `npx sb init` sequentially for each project to setup Storybook for each of them to automatically create the `.storybook` folder and create the necessary configuration in your `angular.json`. - -You can then use [Storybook composition](https://storybook.js.org/docs/angular/sharing/storybook-composition) to composite multiple Storybooks into one. - ---- - -Storybook also comes with a lot of [addons](https://storybook.js.org/addons) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/docs/angular/sharing/publish-storybook) of your Storybook and deploy it anywhere you want. +See [documentation](https://storybook.js.org/docs/8.0/get-started/angular?renderer=angular) for installation instructions, usage examples, APIs, and more. diff --git a/code/frameworks/angular/package.json b/code/frameworks/angular/package.json index b58cb37738cf..9ffa620ac401 100644 --- a/code/frameworks/angular/package.json +++ b/code/frameworks/angular/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/angular", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for Angular: Develop Angular components in isolation with hot reloading.", "keywords": [ "storybook", diff --git a/code/frameworks/angular/template/cli/header.stories.ts b/code/frameworks/angular/template/cli/header.stories.ts index 3222518ace44..3f3fb684e855 100644 --- a/code/frameworks/angular/template/cli/header.stories.ts +++ b/code/frameworks/angular/template/cli/header.stories.ts @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/angular'; import { HeaderComponent } from './header.component'; +import { fn } from '@storybook/test'; const meta: Meta = { title: 'Example/Header', @@ -11,6 +12,11 @@ const meta: Meta = { // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout layout: 'fullscreen', }, + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), + }, }; export default meta; diff --git a/code/frameworks/angular/template/stories/core/applicationConfig/with-browser-animations.stories.ts b/code/frameworks/angular/template/stories/core/applicationConfig/with-browser-animations.stories.ts index f61db00d8f0e..ef30854c26f9 100644 --- a/code/frameworks/angular/template/stories/core/applicationConfig/with-browser-animations.stories.ts +++ b/code/frameworks/angular/template/stories/core/applicationConfig/with-browser-animations.stories.ts @@ -1,7 +1,6 @@ import { Meta, StoryObj, applicationConfig } from '@storybook/angular'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { within, userEvent } from '@storybook/testing-library'; -import { expect } from '@storybook/test'; +import { within, userEvent, expect } from '@storybook/test'; import { importProvidersFrom } from '@angular/core'; import { OpenCloseComponent } from '../moduleMetadata/angular-src/open-close-component/open-close.component'; diff --git a/code/frameworks/angular/template/stories/core/applicationConfig/with-noop-browser-animations.stories.ts b/code/frameworks/angular/template/stories/core/applicationConfig/with-noop-browser-animations.stories.ts index 1a4341ec77cf..3369b9949d33 100644 --- a/code/frameworks/angular/template/stories/core/applicationConfig/with-noop-browser-animations.stories.ts +++ b/code/frameworks/angular/template/stories/core/applicationConfig/with-noop-browser-animations.stories.ts @@ -1,7 +1,6 @@ import { Meta, StoryObj } from '@storybook/angular'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { within, userEvent } from '@storybook/testing-library'; -import { expect } from '@storybook/test'; +import { within, userEvent, expect } from '@storybook/test'; import { importProvidersFrom } from '@angular/core'; import { OpenCloseComponent } from '../moduleMetadata/angular-src/open-close-component/open-close.component'; diff --git a/code/frameworks/ember/package.json b/code/frameworks/ember/package.json index f279db33b3ed..9c796b2f5b7c 100644 --- a/code/frameworks/ember/package.json +++ b/code/frameworks/ember/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/ember", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.", "homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/ember", "bugs": { diff --git a/code/frameworks/ember/template/cli/Button.stories.js b/code/frameworks/ember/template/cli/Button.stories.js index 61de1a4f9cc9..c8fffd70feb0 100644 --- a/code/frameworks/ember/template/cli/Button.stories.js +++ b/code/frameworks/ember/template/cli/Button.stories.js @@ -1,6 +1,7 @@ import { hbs } from 'ember-cli-htmlbars'; import { action } from '@storybook/addon-actions'; import { linkTo } from '@storybook/addon-links'; +import { fn } from '@storybook/test'; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories export default { @@ -14,20 +15,19 @@ export default { }, // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/ember/writing-docs/autodocs tags: ['autodocs'], + args: { onClick: fn() }, }; // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args export const Text = { args: { label: 'Button', - onClick: action('onClick'), }, }; export const Emoji = { args: { label: 'πŸ˜€ 😎 πŸ‘ πŸ’―', - onClick: action('onClick'), }, }; diff --git a/code/frameworks/html-vite/package.json b/code/frameworks/html-vite/package.json index c4004391a219..26bcfa5687c7 100644 --- a/code/frameworks/html-vite/package.json +++ b/code/frameworks/html-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html-vite", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for HTML and Vite: Develop HTML in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/html-webpack5/package.json b/code/frameworks/html-webpack5/package.json index 81cc4e41b756..7c6e27ae25e1 100644 --- a/code/frameworks/html-webpack5/package.json +++ b/code/frameworks/html-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html-webpack5", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index 73b56bd55c4b..f3a10ee4210d 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/nextjs", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for Next.js", "keywords": [ "storybook", diff --git a/code/frameworks/nextjs/template/cli/js/Header.stories.js b/code/frameworks/nextjs/template/cli/js/Header.stories.js index a1d32b3ad65e..982cd970fb5c 100644 --- a/code/frameworks/nextjs/template/cli/js/Header.stories.js +++ b/code/frameworks/nextjs/template/cli/js/Header.stories.js @@ -1,3 +1,4 @@ +import { fn } from '@storybook/test'; import { Header } from './Header'; export default { @@ -9,6 +10,11 @@ export default { // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout layout: 'fullscreen', }, + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), + }, }; export const LoggedIn = { args: { diff --git a/code/frameworks/nextjs/template/cli/ts-3-8/Header.stories.ts b/code/frameworks/nextjs/template/cli/ts-3-8/Header.stories.ts index 82a109720879..feddeae98faf 100644 --- a/code/frameworks/nextjs/template/cli/ts-3-8/Header.stories.ts +++ b/code/frameworks/nextjs/template/cli/ts-3-8/Header.stories.ts @@ -1,4 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; + import { Header } from './Header'; const meta: Meta = { @@ -10,6 +12,11 @@ const meta: Meta = { // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout layout: 'fullscreen', }, + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), + }, }; export default meta; diff --git a/code/frameworks/nextjs/template/cli/ts-4-9/Header.stories.ts b/code/frameworks/nextjs/template/cli/ts-4-9/Header.stories.ts index 046982e62673..39d15874f4c1 100644 --- a/code/frameworks/nextjs/template/cli/ts-4-9/Header.stories.ts +++ b/code/frameworks/nextjs/template/cli/ts-4-9/Header.stories.ts @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; import { Header } from './Header'; const meta = { @@ -10,6 +11,11 @@ const meta = { // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout layout: 'fullscreen', }, + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), + }, } satisfies Meta; export default meta; diff --git a/code/frameworks/nextjs/template/stories/Image.stories.jsx b/code/frameworks/nextjs/template/stories/Image.stories.jsx index 8fa4f6a53de1..79ab308e1286 100644 --- a/code/frameworks/nextjs/template/stories/Image.stories.jsx +++ b/code/frameworks/nextjs/template/stories/Image.stories.jsx @@ -1,6 +1,5 @@ import React, { useRef, useState } from 'react'; import Image from 'next/image'; -import { waitFor } from '@storybook/testing-library'; import Accessibility from '../../assets/accessibility.svg'; import AvifImage from '../../assets/avif-test-image.avif'; diff --git a/code/frameworks/nextjs/template/stories_nextjs-default-js/Head.stories.jsx b/code/frameworks/nextjs/template/stories_nextjs-default-js/Head.stories.jsx index f031096d6ced..1e43bb39eba6 100644 --- a/code/frameworks/nextjs/template/stories_nextjs-default-js/Head.stories.jsx +++ b/code/frameworks/nextjs/template/stories_nextjs-default-js/Head.stories.jsx @@ -1,7 +1,6 @@ -import { expect } from '@storybook/test'; import Head from 'next/head'; import React from 'react'; -import { within, userEvent, waitFor } from '@storybook/testing-library'; +import { waitFor, expect } from '@storybook/test'; function Component() { return ( diff --git a/code/frameworks/preact-vite/package.json b/code/frameworks/preact-vite/package.json index e059321937b6..b8b67128ef22 100644 --- a/code/frameworks/preact-vite/package.json +++ b/code/frameworks/preact-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact-vite", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for Preact and Vite: Develop Preact components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/preact-webpack5/package.json b/code/frameworks/preact-webpack5/package.json index 1bfffc99ab65..51bdc48718ad 100644 --- a/code/frameworks/preact-webpack5/package.json +++ b/code/frameworks/preact-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact-webpack5", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for Preact: Develop Preact Component in isolation.", "keywords": [ "storybook" diff --git a/code/frameworks/react-vite/README.md b/code/frameworks/react-vite/README.md index e8a35450aec9..272f8f50d55f 100644 --- a/code/frameworks/react-vite/README.md +++ b/code/frameworks/react-vite/README.md @@ -1 +1,3 @@ -# Storybook for React +# Storybook for React & Vite + +See [documentation](https://storybook.js.org/docs/8.0/get-started/react-vite?renderer=react) for installation instructions, usage examples, APIs, and more. \ No newline at end of file diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json index 5673d4caf9dd..da08584b7625 100644 --- a/code/frameworks/react-vite/package.json +++ b/code/frameworks/react-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-vite", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for React and Vite: Develop React components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/react-webpack5/README.md b/code/frameworks/react-webpack5/README.md index b3b1d877eaa2..53e3de782715 100644 --- a/code/frameworks/react-webpack5/README.md +++ b/code/frameworks/react-webpack5/README.md @@ -1,47 +1,3 @@ -# Storybook for React +# Storybook for React & Webpack -Storybook for React is a UI development environment for your React components. -With it, you can visualize different states of your UI components and develop them interactively. - -![Storybook Screenshot](https://github.com/storybookjs/storybook/blob/main/media/storybook-intro.gif) - -Storybook runs outside of your app. -So you can develop UI components in isolation without worrying about app specific dependencies and requirements. - -## Getting Started - -```sh -cd my-react-app -npx storybook@latest init -``` - -For more information visit: [storybook.js.org](https://storybook.js.org) - ---- - -Storybook also comes with a lot of [addons](https://storybook.js.org/addons) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/docs/react/sharing/publish-storybook) of your Storybook and deploy it anywhere you want. - -Here are some featured storybooks that you can reference to see how Storybook works: - -- [Demo of Storybook Design System](https://storybook.js.org/design-system) - [source](https://github.com/storybookjs/design-system) - -## Create React App - -Support for [Create React App](https://create-react-app.dev/) is handled by [`@storybook/preset-create-react-app`](https://github.com/storybookjs/presets/tree/master/packages/preset-create-react-app). - -This preset enables support for all Create React App features, including Sass/SCSS and TypeScript. - -If you're working on an app that was initialized manually (i.e., without the use of Create React App), ensure that your app has [react-dom](https://www.npmjs.com/package/react-dom) included as a dependency. Failing to do so can lead to unforeseen issues with Storybook and your project. - -## Typescript - -`@storybook/react` is now exporting its own types to use with Typescript. -You don't need to have `@types/storybook__react` installed anymore if it was your case. -But you probably also need to use types from `@types/node @types/react`. - -## Docs - -- [Basics](https://storybook.js.org/docs/react/get-started) -- [Configurations](https://storybook.js.org/docs/react/configure) -- [Addons](https://storybook.js.org/docs/react/configure/storybook-addons) +See [documentation](https://storybook.js.org/docs/8.0/get-started/react-webpack5?renderer=react) for installation instructions, usage examples, APIs, and more. \ No newline at end of file diff --git a/code/frameworks/react-webpack5/package.json b/code/frameworks/react-webpack5/package.json index 448905c2a899..3d5ceb3ba80c 100644 --- a/code/frameworks/react-webpack5/package.json +++ b/code/frameworks/react-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-webpack5", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/server-webpack5/package.json b/code/frameworks/server-webpack5/package.json index 8b00bc35433d..6aa05a0c122d 100644 --- a/code/frameworks/server-webpack5/package.json +++ b/code/frameworks/server-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/server-webpack5", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/svelte-vite/README.md b/code/frameworks/svelte-vite/README.md index 30a7c36ca01e..1f1dc740151d 100644 --- a/code/frameworks/svelte-vite/README.md +++ b/code/frameworks/svelte-vite/README.md @@ -1 +1,3 @@ -# Storybook for Svelte +# Storybook for Svelte & Vite + +See [documentation](https://storybook.js.org/docs/8.0/get-started/svelte-vite?renderer=svelte) for installation instructions, usage examples, APIs, and more. diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json index be8d6b0c228e..9090da475670 100644 --- a/code/frameworks/svelte-vite/package.json +++ b/code/frameworks/svelte-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte-vite", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for Svelte and Vite: Develop Svelte components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/svelte-webpack5/package.json b/code/frameworks/svelte-webpack5/package.json index 3979794e33b1..db4ed258f40c 100644 --- a/code/frameworks/svelte-webpack5/package.json +++ b/code/frameworks/svelte-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte-webpack5", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/sveltekit/package.json b/code/frameworks/sveltekit/package.json index 8dd860c61d21..2acf401c3f24 100644 --- a/code/frameworks/sveltekit/package.json +++ b/code/frameworks/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/sveltekit", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for SvelteKit", "keywords": [ "storybook", diff --git a/code/frameworks/vue3-vite/README.md b/code/frameworks/vue3-vite/README.md index bb1eb15f980e..08eb5ae95c13 100644 --- a/code/frameworks/vue3-vite/README.md +++ b/code/frameworks/vue3-vite/README.md @@ -1,44 +1,3 @@ -# Storybook for Vue 3 and Vite +# Storybook for Vue and Vite -Storybook for Vue 3 is a UI development environment for your Vue 3 components. -With it, you can visualize different states of your UI components and develop them interactively. - -![Storybook Screenshot](https://github.com/storybookjs/storybook/blob/main/media/storybook-intro.gif) - -Storybook runs outside of your app. -So you can develop UI components in isolation without worrying about app specific dependencies and requirements. - -## Getting Started - -```sh -cd my-vue3-app -npx storybook@latest init -``` - -For more information visit: [storybook.js.org](https://storybook.js.org) - ---- - -Storybook also comes with a lot of [addons](https://storybook.js.org/addons) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/docs/sharing/publish-storybook) of your Storybook and deploy it anywhere you want. - -## Extending the Vue application - -Storybook creates a [Vue 3 application](https://vuejs.org/api/application.html#application-api) for your component preview. -When using global custom components (`app.component`), directives (`app.directive`), extensions (`app.use`), or other application methods, you will need to configure those in the `./storybook/preview.js` file. - -Therefore, Storybook provides you with a `setup` function exported from this package, which receives as a callback your Storybook instance, which you can interact with and add your custom configuration. - -```js -// .storybook/preview.js - -import { setup } from '@storybook/vue3'; - -setup((app) => { - app.use(MyPlugin); - app.component('my-component', MyComponent); - app.mixin({ - /* My mixin */ - }); -}); -``` +See [documentation](https://storybook.js.org/docs/8.0/get-started/vue3-vite?renderer=vue) for installation instructions, usage examples, APIs, and more. diff --git a/code/frameworks/vue3-vite/package.json b/code/frameworks/vue3-vite/package.json index ad452ade8d46..44297c5a4047 100644 --- a/code/frameworks/vue3-vite/package.json +++ b/code/frameworks/vue3-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3-vite", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for Vue3 and Vite: Develop Vue3 components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts b/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts index bd6a22fe338a..7ec671bddf62 100644 --- a/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts +++ b/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts @@ -94,7 +94,13 @@ export async function vueComponentMeta(): Promise { // we can only add the "__docgenInfo" to variables that are actually defined in the current file // so e.g. re-exports like "export { default as MyComponent } from './MyComponent.vue'" must be ignored // to prevent runtime errors - if (new RegExp(`export {.*${name}.*}`).test(src)) { + if ( + new RegExp(`export {.*${name}.*}`).test(src) || + new RegExp(`export \\* from ['"]\\S*${name}['"]`).test(src) || + // when using re-exports, some exports might be resolved via checker.getExportNames + // but are not directly exported inside the current file so we need to ignore them too + !src.includes(name) + ) { return; } diff --git a/code/frameworks/vue3-webpack5/package.json b/code/frameworks/vue3-webpack5/package.json index 6b6504531dbf..094fb5103f2d 100644 --- a/code/frameworks/vue3-webpack5/package.json +++ b/code/frameworks/vue3-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3-webpack5", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for Vue 3: Develop Vue 3 Components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/web-components-vite/package.json b/code/frameworks/web-components-vite/package.json index c4151a92ca89..cbe4ffdc247d 100644 --- a/code/frameworks/web-components-vite/package.json +++ b/code/frameworks/web-components-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components-vite", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for web-components and Vite: Develop Web Components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/web-components-webpack5/package.json b/code/frameworks/web-components-webpack5/package.json index 7e0ab8ec6130..866ab3d35e5d 100644 --- a/code/frameworks/web-components-webpack5/package.json +++ b/code/frameworks/web-components-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components-webpack5", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook for web-components: View web components snippets in isolation with Hot Reloading.", "keywords": [ "lit", diff --git a/code/lib/channels/package.json b/code/lib/channels/package.json index 6abecbb236fc..1f583dc9865d 100644 --- a/code/lib/channels/package.json +++ b/code/lib/channels/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/channels", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "", "keywords": [ "storybook" @@ -47,7 +47,6 @@ "@storybook/client-logger": "workspace:*", "@storybook/core-events": "workspace:*", "@storybook/global": "^5.0.0", - "qs": "^6.10.0", "telejson": "^7.2.0", "tiny-invariant": "^1.3.1" }, diff --git a/code/lib/channels/src/index.test.ts b/code/lib/channels/src/index.test.ts index 07f6d605af10..f99e04f6099c 100644 --- a/code/lib/channels/src/index.test.ts +++ b/code/lib/channels/src/index.test.ts @@ -1,9 +1,43 @@ import { describe, beforeEach, it, expect, vi } from 'vitest'; import type { ChannelTransport, Listener } from '.'; -import { Channel } from '.'; +import { Channel, WebsocketTransport } from '.'; vi.useFakeTimers(); +const MockedWebsocket = vi.hoisted(() => { + const ref = { current: undefined as unknown as InstanceType }; + class MyMockedWebsocket { + onopen: () => void; + + onmessage: (event: { data: string }) => void; + + onerror: (e: any) => void; + + onclose: () => void; + + constructor(url: string) { + this.onopen = vi.fn(); + this.onmessage = vi.fn(); + this.onerror = vi.fn(); + this.onclose = vi.fn(); + + ref.current = this; + } + + send(data: string) { + this.onmessage({ data }); + } + } + return { MyMockedWebsocket, ref }; +}); + +vi.mock('@storybook/global', () => ({ + global: { + ...global, + WebSocket: MockedWebsocket.MyMockedWebsocket, + }, +})); + describe('Channel', () => { let transport: ChannelTransport; let channel: Channel; @@ -232,3 +266,78 @@ describe('Channel', () => { }); }); }); + +describe('WebsocketTransport', () => { + it('should connect', async () => { + const onError = vi.fn(); + const handler = vi.fn(); + + const transport = new WebsocketTransport({ + url: 'ws://localhost:6006', + page: 'preview', + onError, + }); + + transport.setHandler(handler); + MockedWebsocket.ref.current.onopen(); + + expect(handler).toHaveBeenCalledTimes(0); + }); + it('should send message upon disconnect', async () => { + const onError = vi.fn(); + const handler = vi.fn(); + + const transport = new WebsocketTransport({ + url: 'ws://localhost:6006', + page: 'preview', + onError, + }); + + transport.setHandler(handler); + MockedWebsocket.ref.current.onclose(); + + expect(handler.mock.calls[0][0]).toMatchInlineSnapshot(` + { + "args": [], + "from": "preview", + "type": "channelWSDisconnect", + } + `); + }); + it('should send message when send', async () => { + const onError = vi.fn(); + const handler = vi.fn(); + + const transport = new WebsocketTransport({ + url: 'ws://localhost:6006', + page: 'preview', + onError, + }); + + transport.setHandler(handler); + MockedWebsocket.ref.current.send('{ "type": "test", "args": [], "from": "preview" }'); + + expect(handler.mock.calls[0][0]).toMatchInlineSnapshot(` + { + "args": [], + "from": "preview", + "type": "test", + } + `); + }); + it('should call onError handler', async () => { + const onError = vi.fn(); + const handler = vi.fn(); + + const transport = new WebsocketTransport({ + url: 'ws://localhost:6006', + page: 'preview', + onError, + }); + + transport.setHandler(handler); + MockedWebsocket.ref.current.onerror(new Error('testError')); + + expect(onError.mock.calls[0][0]).toMatchInlineSnapshot(`[Error: testError]`); + }); +}); diff --git a/code/lib/channels/src/index.ts b/code/lib/channels/src/index.ts index 7942d57fa4f3..80a865f31904 100644 --- a/code/lib/channels/src/index.ts +++ b/code/lib/channels/src/index.ts @@ -35,7 +35,7 @@ export function createBrowserChannel({ page, extraTransports = [] }: Options): C const { hostname, port } = window.location; const channelUrl = `${protocol}://${hostname}:${port}/storybook-server-channel`; - transports.push(new WebsocketTransport({ url: channelUrl, onError: () => {} })); + transports.push(new WebsocketTransport({ url: channelUrl, onError: () => {}, page })); } return new Channel({ transports }); diff --git a/code/lib/channels/src/postmessage/getEventSourceUrl.ts b/code/lib/channels/src/postmessage/getEventSourceUrl.ts index da3f4ec4d2f6..33c5729a7a08 100644 --- a/code/lib/channels/src/postmessage/getEventSourceUrl.ts +++ b/code/lib/channels/src/postmessage/getEventSourceUrl.ts @@ -7,6 +7,14 @@ export const getEventSourceUrl = (event: MessageEvent) => { // try to find the originating iframe by matching it's contentWindow // This might not be cross-origin safe const [frame, ...remainder] = frames.filter((element) => { + try { + return ( + element.contentWindow?.location.origin === (event.source as Window).location.origin && + element.contentWindow?.location.pathname === (event.source as Window).location.pathname + ); + } catch (err) { + // continue + } try { return element.contentWindow === event.source; } catch (err) { diff --git a/code/lib/channels/src/postmessage/index.ts b/code/lib/channels/src/postmessage/index.ts index 267e8a34f18a..1320a6577b4c 100644 --- a/code/lib/channels/src/postmessage/index.ts +++ b/code/lib/channels/src/postmessage/index.ts @@ -5,7 +5,6 @@ import { global } from '@storybook/global'; import * as EVENTS from '@storybook/core-events'; import { logger, pretty } from '@storybook/client-logger'; import { isJSON, parse, stringify } from 'telejson'; -import qs from 'qs'; import invariant from 'tiny-invariant'; import type { ChannelTransport, @@ -102,13 +101,13 @@ export class PostMessageTransport implements ChannelTransport { const frames = this.getFrames(target); - const query = qs.parse(location?.search || '', { ignoreQueryPrefix: true }); + const query = new URLSearchParams(location?.search || ''); const data = stringify( { key: KEY, event, - refId: query.refId, + refId: query.get('refId'), }, stringifyOptions ); diff --git a/code/lib/channels/src/websocket/index.ts b/code/lib/channels/src/websocket/index.ts index a46df1c28610..0cc73345507d 100644 --- a/code/lib/channels/src/websocket/index.ts +++ b/code/lib/channels/src/websocket/index.ts @@ -5,13 +5,14 @@ import { global } from '@storybook/global'; import { isJSON, parse, stringify } from 'telejson'; import invariant from 'tiny-invariant'; -import type { ChannelTransport, ChannelHandler } from '../types'; +import * as EVENTS from '@storybook/core-events'; +import type { ChannelTransport, ChannelHandler, Config } from '../types'; const { WebSocket } = global; type OnError = (message: Event) => void; -interface WebsocketTransportArgs { +interface WebsocketTransportArgs extends Partial { url: string; onError: OnError; } @@ -25,7 +26,7 @@ export class WebsocketTransport implements ChannelTransport { private isReady = false; - constructor({ url, onError }: WebsocketTransportArgs) { + constructor({ url, onError, page }: WebsocketTransportArgs) { this.socket = new WebSocket(url); this.socket.onopen = () => { this.isReady = true; @@ -41,6 +42,10 @@ export class WebsocketTransport implements ChannelTransport { onError(e); } }; + this.socket.onclose = () => { + invariant(this.handler, 'WebsocketTransport handler should be set'); + this.handler({ type: EVENTS.CHANNEL_WS_DISCONNECT, args: [], from: page || 'preview' }); + }; } setHandler(handler: ChannelHandler) { diff --git a/code/lib/cli-sb/package.json b/code/lib/cli-sb/package.json index fb2741dc33a2..e707547191dd 100644 --- a/code/lib/cli-sb/package.json +++ b/code/lib/cli-sb/package.json @@ -1,6 +1,6 @@ { "name": "sb", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook CLI", "keywords": [ "storybook" diff --git a/code/lib/cli-storybook/package.json b/code/lib/cli-storybook/package.json index f4e2319a57cb..3eb1547fccc0 100644 --- a/code/lib/cli-storybook/package.json +++ b/code/lib/cli-storybook/package.json @@ -1,6 +1,6 @@ { "name": "storybook", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook CLI", "keywords": [ "storybook" diff --git a/code/lib/cli/bin/index.js b/code/lib/cli/bin/index.js index 7131e95a311d..af7869a6e043 100755 --- a/code/lib/cli/bin/index.js +++ b/code/lib/cli/bin/index.js @@ -1,8 +1,8 @@ #!/usr/bin/env node -const majorNodeVersion = parseInt(process.version.toString().replace('v', '').split('.')[0], 10); -if (majorNodeVersion < 16) { - console.error('To run storybook you need to have node 16 or higher'); +const majorNodeVersion = parseInt(process.versions.node, 10); +if (majorNodeVersion < 18) { + console.error('To run Storybook you need to have Node.js 18 or higher'); process.exit(1); } diff --git a/code/lib/cli/package.json b/code/lib/cli/package.json index ed8b9f90bcf6..01d064068c60 100644 --- a/code/lib/cli/package.json +++ b/code/lib/cli/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/cli", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook's CLI - install, dev, build, upgrade, and more", "keywords": [ "cli", diff --git a/code/lib/cli/src/add.test.ts b/code/lib/cli/src/add.test.ts new file mode 100644 index 000000000000..3025da275e49 --- /dev/null +++ b/code/lib/cli/src/add.test.ts @@ -0,0 +1,148 @@ +import { describe, expect, test, vi } from 'vitest'; +import { add, getVersionSpecifier } from './add'; + +const MockedConfig = vi.hoisted(() => { + return { + appendValueToArray: vi.fn(), + }; +}); +const MockedPackageManager = vi.hoisted(() => { + return { + retrievePackageJson: vi.fn(() => ({})), + latestVersion: vi.fn(() => '1.0.0'), + addDependencies: vi.fn(() => {}), + type: 'npm', + }; +}); +const MockedPostInstall = vi.hoisted(() => { + return { + postinstallAddon: vi.fn(), + }; +}); +const MockedConsole = { + log: vi.fn(), + warn: vi.fn(), + error: vi.fn(), +} as any as Console; + +vi.mock('@storybook/csf-tools', () => { + return { + readConfig: vi.fn(() => MockedConfig), + writeConfig: vi.fn(), + }; +}); +vi.mock('./postinstallAddon', () => { + return MockedPostInstall; +}); +vi.mock('@storybook/core-common', () => { + return { + getStorybookInfo: vi.fn(() => ({ mainConfig: {}, configDir: '' })), + serverRequire: vi.fn(() => ({})), + JsPackageManagerFactory: { + getPackageManager: vi.fn(() => MockedPackageManager), + }, + getCoercedStorybookVersion: vi.fn(() => '8.0.0'), + versions: { + '@storybook/addon-docs': '^8.0.0', + }, + }; +}); + +describe('getVersionSpecifier', (it) => { + test.each([ + ['@storybook/addon-docs', ['@storybook/addon-docs', undefined]], + ['@storybook/addon-docs@7.0.1', ['@storybook/addon-docs', '7.0.1']], + ['@storybook/addon-docs@7.0.1-beta.1', ['@storybook/addon-docs', '7.0.1-beta.1']], + ['@storybook/addon-docs@~7.0.1-beta.1', ['@storybook/addon-docs', '~7.0.1-beta.1']], + ['@storybook/addon-docs@^7.0.1-beta.1', ['@storybook/addon-docs', '^7.0.1-beta.1']], + ['@storybook/addon-docs@next', ['@storybook/addon-docs', 'next']], + ])('%s => %s', (input, expected) => { + const result = getVersionSpecifier(input); + expect(result[0]).toEqual(expected[0]); + expect(result[1]).toEqual(expected[1]); + }); +}); + +describe('add', () => { + const testData = [ + { input: 'aa', expected: 'aa@^1.0.0' }, // resolves to the latest version + { input: 'aa@4', expected: 'aa@^4' }, + { input: 'aa@4.1.0', expected: 'aa@^4.1.0' }, + { input: 'aa@^4', expected: 'aa@^4' }, + { input: 'aa@~4', expected: 'aa@~4' }, + { input: 'aa@4.1.0-alpha.1', expected: 'aa@^4.1.0-alpha.1' }, + { input: 'aa@next', expected: 'aa@next' }, + + { input: '@org/aa', expected: '@org/aa@^1.0.0' }, + { input: '@org/aa@4', expected: '@org/aa@^4' }, + { input: '@org/aa@4.1.0', expected: '@org/aa@^4.1.0' }, + { input: '@org/aa@4.1.0-alpha.1', expected: '@org/aa@^4.1.0-alpha.1' }, + { input: '@org/aa@next', expected: '@org/aa@next' }, + + { input: '@storybook/addon-docs@~4', expected: '@storybook/addon-docs@~4' }, + { input: '@storybook/addon-docs@next', expected: '@storybook/addon-docs@next' }, + { input: '@storybook/addon-docs', expected: '@storybook/addon-docs@^8.0.0' }, // takes it from the versions file + ]; + + test.each(testData)('$input', async ({ input, expected }) => { + const [packageName] = getVersionSpecifier(input); + + await add(input, { packageManager: 'npm', skipPostinstall: true }, MockedConsole); + + expect(MockedConfig.appendValueToArray).toHaveBeenCalledWith( + expect.arrayContaining(['addons']), + packageName + ); + + expect(MockedPackageManager.addDependencies).toHaveBeenCalledWith( + { installAsDevDependencies: true }, + [expected] + ); + }); +}); + +describe('add (extra)', () => { + test('not warning when installing the correct version of storybook', async () => { + await add( + '@storybook/addon-docs', + { packageManager: 'npm', skipPostinstall: true }, + MockedConsole + ); + + expect(MockedConsole.warn).not.toHaveBeenCalledWith( + expect.stringContaining(`is not the same as the version of Storybook you are using.`) + ); + }); + test('not warning when installing unrelated package', async () => { + await add('aa', { packageManager: 'npm', skipPostinstall: true }, MockedConsole); + + expect(MockedConsole.warn).not.toHaveBeenCalledWith( + expect.stringContaining(`is not the same as the version of Storybook you are using.`) + ); + }); + test('warning when installing a core addon mismatching version of storybook', async () => { + await add( + '@storybook/addon-docs@2.0.0', + { packageManager: 'npm', skipPostinstall: true }, + MockedConsole + ); + + expect(MockedConsole.warn).toHaveBeenCalledWith( + expect.stringContaining( + `The version of @storybook/addon-docs you are installing is not the same as the version of Storybook you are using. This may lead to unexpected behavior.` + ) + ); + }); + + test('postInstall', async () => { + await add( + '@storybook/addon-docs', + { packageManager: 'npm', skipPostinstall: false }, + MockedConsole + ); + + expect(MockedPostInstall.postinstallAddon).toHaveBeenCalledWith('@storybook/addon-docs', { + packageManager: 'npm', + }); + }); +}); diff --git a/code/lib/cli/src/add.ts b/code/lib/cli/src/add.ts index 0321ec966fd7..19a4b552fcc0 100644 --- a/code/lib/cli/src/add.ts +++ b/code/lib/cli/src/add.ts @@ -1,44 +1,32 @@ import { getStorybookInfo, serverRequire, - getCoercedStorybookVersion, - isCorePackage, JsPackageManagerFactory, + getCoercedStorybookVersion, type PackageManagerName, + versions, } from '@storybook/core-common'; import { readConfig, writeConfig } from '@storybook/csf-tools'; import { isAbsolute, join } from 'path'; import SemVer from 'semver'; import dedent from 'ts-dedent'; +import { postinstallAddon } from './postinstallAddon'; -const logger = console; - -interface PostinstallOptions { +export interface PostinstallOptions { packageManager: PackageManagerName; } -const postinstallAddon = async (addonName: string, options: PostinstallOptions) => { - try { - const modulePath = require.resolve(`${addonName}/postinstall`, { paths: [process.cwd()] }); - - const postinstall = require(modulePath); - - try { - logger.log(`Running postinstall script for ${addonName}`); - await postinstall(options); - } catch (e) { - logger.error(`Error running postinstall script for ${addonName}`); - logger.error(e); - } - } catch (e) { - // no postinstall script - } -}; - -const getVersionSpecifier = (addon: string) => { - const groups = /^(...*)@(.*)$/.exec(addon); +/** + * Extract the addon name and version specifier from the input string + * @param addon - the input string + * @returns [addonName, versionSpecifier] + * @example + * getVersionSpecifier('@storybook/addon-docs@7.0.1') => ['@storybook/addon-docs', '7.0.1'] + */ +export const getVersionSpecifier = (addon: string) => { + const groups = /^(@{0,1}[^@]+)(?:@(.+))?$/.exec(addon); if (groups) { - return [groups[0], groups[2]] as const; + return [groups[1], groups[2]] as const; } return [addon, undefined] as const; }; @@ -58,6 +46,8 @@ const checkInstalled = (addonName: string, main: any) => { return !!existingAddon; }; +const isCoreAddon = (addonName: string) => Object.hasOwn(versions, addonName); + /** * Install the given addon package and add it to main.js * @@ -71,9 +61,11 @@ const checkInstalled = (addonName: string, main: any) => { */ export async function add( addon: string, - options: { packageManager: PackageManagerName; skipPostinstall: boolean } + options: { packageManager: PackageManagerName; skipPostinstall: boolean }, + logger = console ) { const { packageManager: pkgMgr } = options; + const [addonName, inputVersion] = getVersionSpecifier(addon); const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr }); const packageJson = await packageManager.retrievePackageJson(); @@ -85,43 +77,52 @@ export async function add( `); } - if (checkInstalled(addon, requireMain(configDir))) { - throw new Error(dedent` - Addon ${addon} is already installed; we skipped adding it to your ${mainConfig}. - `); - } - - const [addonName, versionSpecifier] = getVersionSpecifier(addon); - if (!mainConfig) { logger.error('Unable to find storybook main.js config'); return; } + + if (checkInstalled(addonName, requireMain(configDir))) { + throw new Error(dedent` + Addon ${addonName} is already installed; we skipped adding it to your ${mainConfig}. + `); + } + const main = await readConfig(mainConfig); logger.log(`Verifying ${addonName}`); - const latestVersion = await packageManager.latestVersion(addonName); - if (!latestVersion) { - logger.error(`Unknown addon ${addonName}`); - } - // add to package.json - const isStorybookAddon = addonName.startsWith('@storybook/'); - const isAddonFromCore = isCorePackage(addonName); const storybookVersion = await getCoercedStorybookVersion(packageManager); - const version = versionSpecifier || (isAddonFromCore ? storybookVersion : latestVersion); - const addonWithVersion = SemVer.valid(version) + let version = inputVersion; + + if (!version && isCoreAddon(addonName) && storybookVersion) { + version = storybookVersion; + } + if (!version) { + version = await packageManager.latestVersion(addonName); + } + + if (isCoreAddon(addonName) && version !== storybookVersion) { + logger.warn( + `The version of ${addonName} you are installing is not the same as the version of Storybook you are using. This may lead to unexpected behavior.` + ); + } + + const addonWithVersion = isValidVersion(version) ? `${addonName}@^${version}` : `${addonName}@${version}`; + logger.log(`Installing ${addonWithVersion}`); await packageManager.addDependencies({ installAsDevDependencies: true }, [addonWithVersion]); - // add to main.js logger.log(`Adding '${addon}' to main.js addons field.`); main.appendValueToArray(['addons'], addonName); await writeConfig(main); - if (!options.skipPostinstall && isStorybookAddon) { + if (!options.skipPostinstall && isCoreAddon(addonName)) { await postinstallAddon(addonName, { packageManager: packageManager.type }); } } +function isValidVersion(version: string) { + return SemVer.valid(version) || version.match(/^\d+$/); +} diff --git a/code/lib/cli/src/automigrate/fixes/addon-postcss.ts b/code/lib/cli/src/automigrate/fixes/addon-postcss.ts index 7978e545a1c5..f8d134183c0b 100644 --- a/code/lib/cli/src/automigrate/fixes/addon-postcss.ts +++ b/code/lib/cli/src/automigrate/fixes/addon-postcss.ts @@ -29,7 +29,7 @@ export const addonPostCSS: Fix = { return dedent` ${chalk.bold( 'Attention' - )}: We've detected that you're using the following package which are incompatible with Storybook 8 and beyond: + )}: We've detected that you're using the following package which is incompatible with Storybook 8 and beyond: - ${chalk.cyan(`@storybook/addon-postcss`)} diff --git a/code/lib/cli/src/automigrate/fixes/addons-api.test.ts b/code/lib/cli/src/automigrate/fixes/addons-api.test.ts new file mode 100644 index 000000000000..2bae14386d3b --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/addons-api.test.ts @@ -0,0 +1,44 @@ +import { addonsAPI } from './addons-api'; +import type { StorybookConfig } from '@storybook/types'; +import type { JsPackageManager } from '@storybook/core-common'; +import { expect, describe, it } from 'vitest'; + +const checkAddonsAPI = async ({ + packageManager, + mainConfig = {}, + storybookVersion = '7.0.0', +}: { + packageManager?: Partial; + mainConfig?: Partial; + storybookVersion?: string; +}) => { + return addonsAPI.check({ + packageManager: packageManager as any, + storybookVersion, + mainConfig: mainConfig as any, + }); +}; + +describe('check function', () => { + it('should return { usesAddonsAPI: true } if @storybook/addons is installed', async () => { + await expect( + checkAddonsAPI({ + packageManager: { + getAllDependencies: async () => ({ + '@storybook/addons': '6.0.0', + }), + }, + }) + ).resolves.toEqual({ usesAddonsAPI: true }); + }); + + it('should return null if @storybook/addons is not installed', async () => { + await expect( + checkAddonsAPI({ + packageManager: { + getAllDependencies: async () => ({}), + }, + }) + ).resolves.toBeNull(); + }); +}); diff --git a/code/lib/cli/src/automigrate/fixes/addons-api.ts b/code/lib/cli/src/automigrate/fixes/addons-api.ts new file mode 100644 index 000000000000..f193898aa82f --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/addons-api.ts @@ -0,0 +1,45 @@ +import chalk from 'chalk'; +import { dedent } from 'ts-dedent'; +import type { Fix } from '../types'; + +interface AddonsAPIRunOptions { + usesAddonsAPI: boolean; +} + +export const addonsAPI: Fix = { + id: 'addons-api', + + versionRange: ['<8', '>=8'], + + promptType: 'notification', + + async check({ packageManager }) { + const allDependencies = await packageManager.getAllDependencies(); + const usesAddonsAPI = !!allDependencies['@storybook/addons']; + + if (!usesAddonsAPI) { + return null; + } + + return { usesAddonsAPI: true }; + }, + + prompt() { + return dedent` + ${chalk.bold( + 'Attention' + )}: We've detected that you're using the following package which is removed in Storybook 8 and beyond: + + - ${chalk.cyan(`@storybook/addons`)} + + This package has been deprecated and replaced with ${chalk.cyan( + `@storybook/preview-api` + )} and ${chalk.cyan(`@storybook/manager-api`)}. + + You can find more information about the new addons API in the migration guide: + ${chalk.yellow( + 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-addons-api' + )} + `; + }, +}; diff --git a/code/lib/cli/src/automigrate/fixes/index.ts b/code/lib/cli/src/automigrate/fixes/index.ts index 91ba9a27927f..022074fa8301 100644 --- a/code/lib/cli/src/automigrate/fixes/index.ts +++ b/code/lib/cli/src/automigrate/fixes/index.ts @@ -23,12 +23,15 @@ import { storyshotsMigration } from './storyshots-migration'; import { removeArgtypesRegex } from './remove-argtypes-regex'; import { webpack5CompilerSetup } from './webpack5-compiler-setup'; import { removeJestTestingLibrary } from './remove-jest-testing-library'; +import { addonsAPI } from './addons-api'; import { mdx1to3 } from './mdx-1-to-3'; import { addonPostCSS } from './addon-postcss'; +import { upgradeStorybookRelatedDependencies } from './upgrade-storybook-related-dependencies'; export * from '../types'; export const allFixes: Fix[] = [ + addonsAPI, newFrameworks, cra5, webpack5, @@ -54,6 +57,7 @@ export const allFixes: Fix[] = [ removeLegacyMDX1, webpack5CompilerSetup, mdx1to3, + upgradeStorybookRelatedDependencies, ]; export const initFixes: Fix[] = [eslintPlugin]; diff --git a/code/lib/cli/src/automigrate/fixes/upgrade-storybook-related-dependencies.test.ts b/code/lib/cli/src/automigrate/fixes/upgrade-storybook-related-dependencies.test.ts new file mode 100644 index 000000000000..0c16309647bd --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/upgrade-storybook-related-dependencies.test.ts @@ -0,0 +1,95 @@ +import { describe, afterEach, it, expect, vi } from 'vitest'; +import type { StorybookConfig } from '@storybook/types'; +import type { JsPackageManager } from '@storybook/core-common'; +import * as docsUtils from '../../doctor/getIncompatibleStorybookPackages'; + +import { upgradeStorybookRelatedDependencies } from './upgrade-storybook-related-dependencies'; + +vi.mock('../../doctor/getIncompatibleStorybookPackages'); + +const check = async ({ + packageManager, + main: mainConfig = {}, + storybookVersion = '8.0.0', +}: { + packageManager: Partial; + main?: Partial & Record; + storybookVersion?: string; +}) => { + return upgradeStorybookRelatedDependencies.check({ + packageManager: packageManager as any, + configDir: '', + mainConfig: mainConfig as any, + storybookVersion, + }); +}; + +describe('upgrade-storybook-related-dependencies fix', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should detect storyshots registered in main.js', async () => { + const analyzedPackages = [ + { + packageName: '@chromatic-com/storybook', + packageVersion: '1.2.9', + availableUpgrade: '2.0.0', + hasIncompatibleDependencies: false, + }, + { + packageName: '@storybook/jest', + packageVersion: '0.2.3', + availableUpgrade: '1.0.0', + hasIncompatibleDependencies: false, + }, + { + packageName: '@storybook/preset-create-react-app', + packageVersion: '3.2.0', + availableUpgrade: '8.0.0', + hasIncompatibleDependencies: true, + }, + { + packageName: 'storybook', + packageVersion: '8.0.0', + availableUpgrade: undefined, + hasIncompatibleDependencies: true, + }, + ]; + vi.mocked(docsUtils.getIncompatibleStorybookPackages).mockResolvedValue(analyzedPackages); + await expect( + check({ + packageManager: { + getAllDependencies: async () => ({ + '@chromatic-com/storybook': '1.2.9', + '@storybook/jest': '0.2.3', + '@storybook/preset-create-react-app': '3.2.0', + storybook: '8.0.0', + }), + latestVersion: async (pkgName) => + analyzedPackages.find((pkg) => pkg.packageName === pkgName)?.availableUpgrade || '', + }, + }) + ).resolves.toMatchInlineSnapshot(` + { + "upgradable": [ + { + "afterVersion": "2.0.0", + "beforeVersion": "1.2.9", + "packageName": "@chromatic-com/storybook", + }, + { + "afterVersion": "1.0.0", + "beforeVersion": "0.2.3", + "packageName": "@storybook/jest", + }, + { + "afterVersion": "8.0.0", + "beforeVersion": "3.2.0", + "packageName": "@storybook/preset-create-react-app", + }, + ], + } + `); + }); +}); diff --git a/code/lib/cli/src/automigrate/fixes/upgrade-storybook-related-dependencies.ts b/code/lib/cli/src/automigrate/fixes/upgrade-storybook-related-dependencies.ts new file mode 100644 index 000000000000..5614b7e35ad0 --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/upgrade-storybook-related-dependencies.ts @@ -0,0 +1,162 @@ +import { dedent } from 'ts-dedent'; +import { cyan, yellow } from 'chalk'; +import { valid, coerce } from 'semver'; +import type { JsPackageManager } from '@storybook/core-common'; +import { isCorePackage } from '@storybook/core-common'; +import type { Fix } from '../types'; +import { getIncompatibleStorybookPackages } from '../../doctor/getIncompatibleStorybookPackages'; + +type PackageMetadata = { + packageName: string; + beforeVersion: string | null; + afterVersion: string | null; +}; + +interface Options { + upgradable: PackageMetadata[]; +} + +async function getLatestVersions( + packageManager: JsPackageManager, + packages: [string, string][] +): Promise { + return Promise.all( + packages.map(async ([packageName, beforeVersion]) => ({ + packageName, + beforeVersion: coerce(beforeVersion)?.toString() || null, + afterVersion: await packageManager.latestVersion(packageName).catch(() => null), + })) + ); +} + +function isPackageUpgradable( + afterVersion: string, + packageName: string, + allDependencies: Record +) { + const installedVersion = coerce(allDependencies[packageName])?.toString(); + + return valid(afterVersion) && afterVersion !== installedVersion; +} + +/** + * Is the user upgrading to the `latest` version of Storybook? + * Let's try to pull along some of the storybook related dependencies to `latest` as well! + * + * We communicate clearly that this migration is a helping hand, but not a complete solution. + * The user should still manually check for other dependencies that might be incompatible. + * + * see: https://github.com/storybookjs/storybook/issues/25731#issuecomment-1977346398 + */ +export const upgradeStorybookRelatedDependencies = { + id: 'upgradeStorybookRelatedDependencies', + versionRange: ['*.*.*', '*.*.*'], + promptType: 'auto', + promptDefaultValue: false, + + async check({ packageManager, storybookVersion }) { + const analyzedPackages = await getIncompatibleStorybookPackages({ + currentStorybookVersion: storybookVersion, + packageManager, + skipErrors: true, + }); + + const allDependencies = (await packageManager.getAllDependencies()) as Record; + const storybookDependencies = Object.keys(allDependencies) + .filter((dep) => dep.includes('storybook')) + .filter((dep) => !isCorePackage(dep)); + const incompatibleDependencies = analyzedPackages + .filter((pkg) => pkg.hasIncompatibleDependencies) + .map((pkg) => pkg.packageName); + + const uniquePackages = Array.from( + new Set([...storybookDependencies, ...incompatibleDependencies]) + ).map((packageName) => [packageName, allDependencies[packageName]]) as [string, string][]; + + const packageVersions = await getLatestVersions(packageManager, uniquePackages); + + const upgradablePackages = packageVersions.filter( + ({ packageName, afterVersion, beforeVersion }) => { + if (beforeVersion === null || afterVersion === null) { + return false; + } + + return isPackageUpgradable(afterVersion, packageName, allDependencies); + } + ); + + return upgradablePackages.length > 0 ? { upgradable: upgradablePackages } : null; + }, + + prompt({ upgradable }) { + return dedent` + You're upgrading to the latest version of Storybook. We recommend upgrading the following packages: + ${upgradable + .map(({ packageName, afterVersion, beforeVersion }) => { + return `- ${cyan(packageName)}: ${cyan(beforeVersion)} => ${cyan(afterVersion)}`; + }) + .join('\n')} + + After upgrading, we will run the dedupe command, which could possibly have effects on dependencies that are not Storybook related. + see: https://docs.npmjs.com/cli/commands/npm-dedupe + + Do you want to proceed (upgrade the detected packages)? + `; + }, + + async run({ result: { upgradable }, packageManager, dryRun }) { + if (dryRun) { + console.log(dedent` + We would have upgrade the following: + ${upgradable + .map( + ({ packageName, afterVersion, beforeVersion }) => + `${packageName}: ${beforeVersion} => ${afterVersion}` + ) + .join('\n')} + `); + return; + } + + if (upgradable.length > 0) { + const packageJson = await packageManager.readPackageJson(); + + upgradable.forEach((item) => { + if (!item) { + return; + } + + const { packageName, afterVersion: version } = item; + const prefixed = `^${version}`; + + if (packageJson.dependencies?.[packageName]) { + packageJson.dependencies[packageName] = prefixed; + } + if (packageJson.devDependencies?.[packageName]) { + packageJson.devDependencies[packageName] = prefixed; + } + if (packageJson.peerDependencies?.[packageName]) { + packageJson.peerDependencies[packageName] = prefixed; + } + }); + + await packageManager.writePackageJson(packageJson); + await packageManager.installDependencies(); + + await packageManager + .executeCommand({ command: 'dedupe', args: [], stdio: 'ignore' }) + .catch(() => {}); + + console.log(); + console.log(dedent` + We upgraded ${yellow(upgradable.length)} packages: + ${upgradable + .map(({ packageName, afterVersion, beforeVersion }) => { + return `- ${cyan(packageName)}: ${cyan(beforeVersion)} => ${cyan(afterVersion)}`; + }) + .join('\n')} + `); + } + console.log(); + }, +} satisfies Fix; diff --git a/code/lib/cli/src/automigrate/fixes/vite-config-file.ts b/code/lib/cli/src/automigrate/fixes/vite-config-file.ts index 6a8dd9ec0e02..9203b45f225f 100644 --- a/code/lib/cli/src/automigrate/fixes/vite-config-file.ts +++ b/code/lib/cli/src/automigrate/fixes/vite-config-file.ts @@ -105,7 +105,7 @@ export const viteConfigFile = { If you do already have these plugins, you can ignore this message. You can find more information on how to do this here: - https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#framework-specific-vite-plugins-have-to-be-explicitly-added + https://storybook.js.org/docs/8.0/migration-guide/#missing-viteconfigjs-file This change was necessary to support newer versions of Vite. `; @@ -115,7 +115,7 @@ export const viteConfigFile = { Please add a vite.config.js file to your project root. You can find more information on how to do this here: - https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#framework-specific-vite-plugins-have-to-be-explicitly-added + https://storybook.js.org/docs/8.0/migration-guide/#missing-viteconfigjs-file This change was necessary to support newer versions of Vite. `; diff --git a/code/lib/cli/src/automigrate/helpers/getMigrationSummary.test.ts b/code/lib/cli/src/automigrate/helpers/getMigrationSummary.test.ts index f43e84370852..eb49848ab2c0 100644 --- a/code/lib/cli/src/automigrate/helpers/getMigrationSummary.test.ts +++ b/code/lib/cli/src/automigrate/helpers/getMigrationSummary.test.ts @@ -129,35 +129,7 @@ describe('getMigrationSummary', () => { The automigrations try to migrate common patterns in your project, but might not contain everything needed to migrate to the latest version of Storybook. Please check the changelog and migration guide for manual migrations and more information: https://storybook.js.org/docs/8.0/migration-guide - And reach out on Discord if you need help: https://discord.gg/storybook - - ───────────────────────────────────────────────── - - Critical: The following dependencies are duplicated and WILL cause unexpected behavior: - - @storybook/instrumenter: - 6.0.0, 7.1.0 - - @storybook/core-common: - 6.0.0, 7.1.0 - - - - - Attention: The following dependencies are duplicated which might cause unexpected behavior: - - @storybook/addon-essentials: - 7.0.0, 7.1.0 - - - - - Please try de-duplicating these dependencies by running yarn dedupe - - - - - You can find more information for a given dependency by running yarn why " + And reach out on Discord if you need help: https://discord.gg/storybook" `); }); diff --git a/code/lib/cli/src/automigrate/helpers/getMigrationSummary.ts b/code/lib/cli/src/automigrate/helpers/getMigrationSummary.ts index aa0503865749..12c8ac07bfa7 100644 --- a/code/lib/cli/src/automigrate/helpers/getMigrationSummary.ts +++ b/code/lib/cli/src/automigrate/helpers/getMigrationSummary.ts @@ -4,7 +4,6 @@ import dedent from 'ts-dedent'; import type { InstallationMetadata } from '@storybook/core-common'; import type { FixSummary } from '../types'; import { FixStatus } from '../types'; -import { getDuplicatedDepsWarnings } from '../../doctor/getDuplicatedDepsWarnings'; export const messageDivider = '\n\n'; const segmentDivider = '\n\n─────────────────────────────────────────────────\n\n'; @@ -75,14 +74,6 @@ export function getMigrationSummary({ And reach out on Discord if you need help: ${chalk.yellow('https://discord.gg/storybook')} `); - const duplicatedDepsMessage = installationMetadata - ? getDuplicatedDepsWarnings(installationMetadata) - : getDuplicatedDepsWarnings(); - - if (duplicatedDepsMessage) { - messages.push(duplicatedDepsMessage.join(messageDivider)); - } - const hasNoFixes = Object.values(fixResults).every((r) => r === FixStatus.UNNECESSARY); const hasFailures = Object.values(fixResults).some( (r) => r === FixStatus.FAILED || r === FixStatus.CHECK_FAILED diff --git a/code/lib/cli/src/automigrate/index.ts b/code/lib/cli/src/automigrate/index.ts index 8a84476b5e93..340a90f7ec38 100644 --- a/code/lib/cli/src/automigrate/index.ts +++ b/code/lib/cli/src/automigrate/index.ts @@ -29,6 +29,9 @@ import { getMigrationSummary } from './helpers/getMigrationSummary'; import { getStorybookData } from './helpers/mainConfigFile'; import { doctor } from '../doctor'; +import { upgradeStorybookRelatedDependencies } from './fixes/upgrade-storybook-related-dependencies'; +import dedent from 'ts-dedent'; + const logger = console; const LOG_FILE_NAME = 'migration-storybook.log'; const LOG_FILE_PATH = join(process.cwd(), LOG_FILE_NAME); @@ -56,8 +59,16 @@ const cleanup = () => { }; const logAvailableMigrations = () => { - const availableFixes = allFixes.map((f) => chalk.yellow(f.id)).join(', '); - logger.info(`\nThe following migrations are available: ${availableFixes}`); + const availableFixes = allFixes + .map((f) => chalk.yellow(f.id)) + .map((x) => `- ${x}`) + .join('\n'); + + console.log(); + logger.info(dedent` + The following migrations are available: + ${availableFixes} + `); }; export const doAutomigrate = async (options: AutofixOptionsFromCLI) => { @@ -84,7 +95,7 @@ export const doAutomigrate = async (options: AutofixOptionsFromCLI) => { throw new Error('Could not determine main config path'); } - await automigrate({ + const outcome = await automigrate({ ...options, packageManager, storybookVersion, @@ -92,9 +103,12 @@ export const doAutomigrate = async (options: AutofixOptionsFromCLI) => { mainConfigPath, configDir, isUpgrade: false, + isLatest: false, }); - await doctor({ configDir, packageManager: options.packageManager }); + if (outcome) { + await doctor({ configDir, packageManager: options.packageManager }); + } }; export const automigrate = async ({ @@ -112,6 +126,7 @@ export const automigrate = async ({ skipInstall, hideMigrationSummary = false, isUpgrade, + isLatest, }: AutofixOptions): Promise<{ fixResults: Record; preCheckFailure?: PreCheckFailure; @@ -121,8 +136,21 @@ export const automigrate = async ({ return null; } - const selectedFixes = inputFixes || allFixes; - const fixes = fixId ? selectedFixes.filter((f) => f.id === fixId) : selectedFixes; + const selectedFixes: Fix[] = + inputFixes || + allFixes.filter((fix) => { + // we only allow this automigration when the user explicitly asks for it, or they are upgrading to the latest version of storybook + if ( + fix.id === upgradeStorybookRelatedDependencies.id && + isLatest === false && + fixId !== upgradeStorybookRelatedDependencies.id + ) { + return false; + } + + return true; + }); + const fixes: Fix[] = fixId ? selectedFixes.filter((f) => f.id === fixId) : selectedFixes; if (fixId && fixes.length === 0) { logger.info(`πŸ“­ No migrations found for ${chalk.magenta(fixId)}.`); @@ -143,7 +171,7 @@ export const automigrate = async ({ mainConfigPath, storybookVersion, beforeVersion, - isUpgrade, + isUpgrade: !!isUpgrade, dryRun, yes, }); @@ -314,7 +342,7 @@ export async function runFixes({ type: 'confirm', name: 'fix', message: `Do you want to run the '${chalk.cyan(f.id)}' migration on your project?`, - initial: true, + initial: f.promptDefaultValue ?? true, }, { onCancel: () => { diff --git a/code/lib/cli/src/automigrate/types.ts b/code/lib/cli/src/automigrate/types.ts index d8cc9f06af3e..43447102162f 100644 --- a/code/lib/cli/src/automigrate/types.ts +++ b/code/lib/cli/src/automigrate/types.ts @@ -1,5 +1,5 @@ -import type { StorybookConfigRaw } from '@storybook/types'; import type { JsPackageManager, PackageManagerName } from '@storybook/core-common'; +import type { StorybookConfigRaw } from '@storybook/types'; export interface CheckOptions { packageManager: JsPackageManager; @@ -37,6 +37,7 @@ type BaseFix = { versionRange: [from: string, to: string]; check: (options: CheckOptions) => Promise; prompt: (result: ResultType) => string; + promptDefaultValue?: boolean; }; type PromptType = @@ -75,6 +76,7 @@ export interface AutofixOptions extends Omit { - const storybookVersion = semver.coerce(currentStorybookVersion); - const packageVersion = semver.coerce(installedVersion); - return storybookVersion?.major !== packageVersion?.major; -}; - export const checkPackageCompatibility = async (dependency: string, context: Context) => { const { currentStorybookVersion, skipErrors, packageManager } = context; try { @@ -46,12 +40,12 @@ export const checkPackageCompatibility = async (dependency: string, context: Con ...peerDependencies, }) .filter(([dep]) => storybookCorePackages[dep as keyof typeof storybookCorePackages]) - .find(([, version]) => { + .find(([_, versionRange]) => { // prevent issues with "tag" based versions e.g. "latest" or "next" instead of actual numbers return ( - version && - semver.validRange(version) && - isPackageIncompatible(version, currentStorybookVersion) + versionRange && + semver.validRange(versionRange) && + !semver.satisfies(currentStorybookVersion, versionRange) ); }); diff --git a/code/lib/cli/src/migrate.ts b/code/lib/cli/src/migrate.ts index 15d2169120e7..29ee10dc82a3 100644 --- a/code/lib/cli/src/migrate.ts +++ b/code/lib/cli/src/migrate.ts @@ -1,11 +1,12 @@ import { listCodemods, runCodemod } from '@storybook/codemod'; -import { runFixes } from './automigrate'; -import { mdxToCSF } from './automigrate/fixes/mdx-to-csf'; import { JsPackageManagerFactory, - getStorybookInfo, getCoercedStorybookVersion, + getStorybookInfo, } from '@storybook/core-common'; + +import { runFixes } from './automigrate'; +import { mdxToCSF } from './automigrate/fixes/mdx-to-csf'; import { getStorybookVersionSpecifier } from './helpers'; const logger = console; diff --git a/code/lib/cli/src/postinstallAddon.ts b/code/lib/cli/src/postinstallAddon.ts new file mode 100644 index 000000000000..50719c29e29c --- /dev/null +++ b/code/lib/cli/src/postinstallAddon.ts @@ -0,0 +1,19 @@ +import type { PostinstallOptions } from './add'; + +export const postinstallAddon = async (addonName: string, options: PostinstallOptions) => { + try { + const modulePath = require.resolve(`${addonName}/postinstall`, { paths: [process.cwd()] }); + + const postinstall = require(modulePath); + + try { + console.log(`Running postinstall script for ${addonName}`); + await postinstall(options); + } catch (e) { + console.error(`Error running postinstall script for ${addonName}`); + console.error(e); + } + } catch (e) { + // no postinstall script + } +}; diff --git a/code/lib/cli/src/sandbox-templates.ts b/code/lib/cli/src/sandbox-templates.ts index 182e49798148..b28021be7049 100644 --- a/code/lib/cli/src/sandbox-templates.ts +++ b/code/lib/cli/src/sandbox-templates.ts @@ -443,7 +443,7 @@ const baseTemplates = { }, 'qwik-vite/default-ts': { name: 'Qwik CLI Latest (Vite | TypeScript)', - script: 'yarn create qwik basic {{beforeDir}}', + script: 'npm create qwik basic {{beforeDir}}', // TODO: The community template does not provide standard stories, which is required for e2e tests. Reenable once it does. inDevelopment: true, expected: { diff --git a/code/lib/cli/src/upgrade.ts b/code/lib/cli/src/upgrade.ts index 179c7d806a2f..f0b7cc5ff612 100644 --- a/code/lib/cli/src/upgrade.ts +++ b/code/lib/cli/src/upgrade.ts @@ -148,6 +148,7 @@ export const doUpgrade = async ({ ]); const isOutdated = lt(currentVersion, latestVersion); + const isExactLatest = currentVersion === latestVersion; const isPrerelease = prerelease(currentVersion) !== null; const borderColor = isOutdated ? '#FC521F' : '#F1618C'; @@ -261,7 +262,8 @@ export const doUpgrade = async ({ mainConfigPath, beforeVersion, storybookVersion: currentVersion, - isUpgrade: true, + isUpgrade: isOutdated, + isLatest: isExactLatest, }); } diff --git a/code/lib/client-logger/package.json b/code/lib/client-logger/package.json index f5f6f32ed843..83926ddd07ac 100644 --- a/code/lib/client-logger/package.json +++ b/code/lib/client-logger/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/client-logger", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "", "keywords": [ "storybook" diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json index cdb05de5da4d..290164c50f5f 100644 --- a/code/lib/codemod/package.json +++ b/code/lib/codemod/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/codemod", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "A collection of codemod scripts written with JSCodeshift", "keywords": [ "storybook" diff --git a/code/lib/core-common/package.json b/code/lib/core-common/package.json index 169977d00655..b46b9a1de983 100644 --- a/code/lib/core-common/package.json +++ b/code/lib/core-common/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-common", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" @@ -52,7 +52,7 @@ "@yarnpkg/libzip": "2.3.0", "chalk": "^4.1.0", "cross-spawn": "^7.0.3", - "esbuild": "^0.18.0", + "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0", "esbuild-register": "^3.5.0", "execa": "^5.0.0", "file-system-cache": "2.3.0", diff --git a/code/lib/core-common/src/utils/__tests__/check-addon-order.test.ts b/code/lib/core-common/src/utils/__tests__/check-addon-order.test.ts index 692402ba5844..db4820b02ec6 100644 --- a/code/lib/core-common/src/utils/__tests__/check-addon-order.test.ts +++ b/code/lib/core-common/src/utils/__tests__/check-addon-order.test.ts @@ -17,6 +17,7 @@ const essentialAddons = [ 'toolbars', 'measure', 'outline', + 'highlight', ]; const pkgName = (entry: CoreCommon_AddonEntry): string => { @@ -42,6 +43,22 @@ afterEach(() => { describe.each([ ['docs', 'controls', ['docs', 'controls']], ['docs', 'controls', ['docs', 'foo/node_modules/@storybook/addon-controls']], + [ + 'actions', + 'interactions', + [ + 'foo\\node_modules\\@storybook\\addon-essentials', + 'foo\\node_modules\\@storybook\\addon-interactions', + ], + ], + [ + 'actions', + 'interactions', + [ + 'foo\\\\node_modules\\\\@storybook\\\\addon-essentials', + 'foo\\\\node_modules\\\\@storybook\\\\addon-interactions', + ], + ], ['docs', 'controls', [{ name: '@storybook/addon-docs' }, 'controls']], ['docs', 'controls', ['essentials', 'controls']], ['docs', 'controls', ['essentials']], diff --git a/code/lib/core-common/src/utils/check-addon-order.ts b/code/lib/core-common/src/utils/check-addon-order.ts index 58faeccee228..b91c93e7df02 100644 --- a/code/lib/core-common/src/utils/check-addon-order.ts +++ b/code/lib/core-common/src/utils/check-addon-order.ts @@ -15,7 +15,7 @@ interface Options { const predicateFor = (addon: string) => (entry: CoreCommon_AddonEntry) => { const name = (entry as CoreCommon_OptionsEntry).name || (entry as string); - return name && name.includes(addon); + return name && name.replaceAll(/(\\){1,2}/g, '/').includes(addon); }; const isCorrectOrder = ( diff --git a/code/lib/core-common/src/versions.ts b/code/lib/core-common/src/versions.ts index 346f3d272c65..0109c9c2a8a3 100644 --- a/code/lib/core-common/src/versions.ts +++ b/code/lib/core-common/src/versions.ts @@ -1,83 +1,83 @@ // auto generated file, do not edit export default { - '@storybook/addon-a11y': '8.0.0-rc.1', - '@storybook/addon-actions': '8.0.0-rc.1', - '@storybook/addon-backgrounds': '8.0.0-rc.1', - '@storybook/addon-controls': '8.0.0-rc.1', - '@storybook/addon-docs': '8.0.0-rc.1', - '@storybook/addon-essentials': '8.0.0-rc.1', - '@storybook/addon-highlight': '8.0.0-rc.1', - '@storybook/addon-interactions': '8.0.0-rc.1', - '@storybook/addon-jest': '8.0.0-rc.1', - '@storybook/addon-links': '8.0.0-rc.1', - '@storybook/addon-mdx-gfm': '8.0.0-rc.1', - '@storybook/addon-measure': '8.0.0-rc.1', - '@storybook/addon-onboarding': '8.0.0-rc.1', - '@storybook/addon-outline': '8.0.0-rc.1', - '@storybook/addon-storysource': '8.0.0-rc.1', - '@storybook/addon-themes': '8.0.0-rc.1', - '@storybook/addon-toolbars': '8.0.0-rc.1', - '@storybook/addon-viewport': '8.0.0-rc.1', - '@storybook/angular': '8.0.0-rc.1', - '@storybook/blocks': '8.0.0-rc.1', - '@storybook/builder-manager': '8.0.0-rc.1', - '@storybook/builder-vite': '8.0.0-rc.1', - '@storybook/builder-webpack5': '8.0.0-rc.1', - '@storybook/channels': '8.0.0-rc.1', - '@storybook/cli': '8.0.0-rc.1', - '@storybook/client-logger': '8.0.0-rc.1', - '@storybook/codemod': '8.0.0-rc.1', - '@storybook/components': '8.0.0-rc.1', - '@storybook/core-common': '8.0.0-rc.1', - '@storybook/core-events': '8.0.0-rc.1', - '@storybook/core-server': '8.0.0-rc.1', - '@storybook/core-webpack': '8.0.0-rc.1', - '@storybook/csf-plugin': '8.0.0-rc.1', - '@storybook/csf-tools': '8.0.0-rc.1', - '@storybook/docs-tools': '8.0.0-rc.1', - '@storybook/ember': '8.0.0-rc.1', - '@storybook/html': '8.0.0-rc.1', - '@storybook/html-vite': '8.0.0-rc.1', - '@storybook/html-webpack5': '8.0.0-rc.1', - '@storybook/instrumenter': '8.0.0-rc.1', - '@storybook/manager': '8.0.0-rc.1', - '@storybook/manager-api': '8.0.0-rc.1', - '@storybook/nextjs': '8.0.0-rc.1', - '@storybook/node-logger': '8.0.0-rc.1', - '@storybook/preact': '8.0.0-rc.1', - '@storybook/preact-vite': '8.0.0-rc.1', - '@storybook/preact-webpack5': '8.0.0-rc.1', - '@storybook/preset-create-react-app': '8.0.0-rc.1', - '@storybook/preset-html-webpack': '8.0.0-rc.1', - '@storybook/preset-preact-webpack': '8.0.0-rc.1', - '@storybook/preset-react-webpack': '8.0.0-rc.1', - '@storybook/preset-server-webpack': '8.0.0-rc.1', - '@storybook/preset-svelte-webpack': '8.0.0-rc.1', - '@storybook/preset-vue3-webpack': '8.0.0-rc.1', - '@storybook/preview': '8.0.0-rc.1', - '@storybook/preview-api': '8.0.0-rc.1', - '@storybook/react': '8.0.0-rc.1', - '@storybook/react-dom-shim': '8.0.0-rc.1', - '@storybook/react-vite': '8.0.0-rc.1', - '@storybook/react-webpack5': '8.0.0-rc.1', - '@storybook/router': '8.0.0-rc.1', - '@storybook/server': '8.0.0-rc.1', - '@storybook/server-webpack5': '8.0.0-rc.1', - '@storybook/source-loader': '8.0.0-rc.1', - '@storybook/svelte': '8.0.0-rc.1', - '@storybook/svelte-vite': '8.0.0-rc.1', - '@storybook/svelte-webpack5': '8.0.0-rc.1', - '@storybook/sveltekit': '8.0.0-rc.1', - '@storybook/telemetry': '8.0.0-rc.1', - '@storybook/test': '8.0.0-rc.1', - '@storybook/theming': '8.0.0-rc.1', - '@storybook/types': '8.0.0-rc.1', - '@storybook/vue3': '8.0.0-rc.1', - '@storybook/vue3-vite': '8.0.0-rc.1', - '@storybook/vue3-webpack5': '8.0.0-rc.1', - '@storybook/web-components': '8.0.0-rc.1', - '@storybook/web-components-vite': '8.0.0-rc.1', - '@storybook/web-components-webpack5': '8.0.0-rc.1', - sb: '8.0.0-rc.1', - storybook: '8.0.0-rc.1', + '@storybook/addon-a11y': '8.1.0-alpha.0', + '@storybook/addon-actions': '8.1.0-alpha.0', + '@storybook/addon-backgrounds': '8.1.0-alpha.0', + '@storybook/addon-controls': '8.1.0-alpha.0', + '@storybook/addon-docs': '8.1.0-alpha.0', + '@storybook/addon-essentials': '8.1.0-alpha.0', + '@storybook/addon-highlight': '8.1.0-alpha.0', + '@storybook/addon-interactions': '8.1.0-alpha.0', + '@storybook/addon-jest': '8.1.0-alpha.0', + '@storybook/addon-links': '8.1.0-alpha.0', + '@storybook/addon-mdx-gfm': '8.1.0-alpha.0', + '@storybook/addon-measure': '8.1.0-alpha.0', + '@storybook/addon-onboarding': '8.1.0-alpha.0', + '@storybook/addon-outline': '8.1.0-alpha.0', + '@storybook/addon-storysource': '8.1.0-alpha.0', + '@storybook/addon-themes': '8.1.0-alpha.0', + '@storybook/addon-toolbars': '8.1.0-alpha.0', + '@storybook/addon-viewport': '8.1.0-alpha.0', + '@storybook/angular': '8.1.0-alpha.0', + '@storybook/blocks': '8.1.0-alpha.0', + '@storybook/builder-manager': '8.1.0-alpha.0', + '@storybook/builder-vite': '8.1.0-alpha.0', + '@storybook/builder-webpack5': '8.1.0-alpha.0', + '@storybook/channels': '8.1.0-alpha.0', + '@storybook/cli': '8.1.0-alpha.0', + '@storybook/client-logger': '8.1.0-alpha.0', + '@storybook/codemod': '8.1.0-alpha.0', + '@storybook/components': '8.1.0-alpha.0', + '@storybook/core-common': '8.1.0-alpha.0', + '@storybook/core-events': '8.1.0-alpha.0', + '@storybook/core-server': '8.1.0-alpha.0', + '@storybook/core-webpack': '8.1.0-alpha.0', + '@storybook/csf-plugin': '8.1.0-alpha.0', + '@storybook/csf-tools': '8.1.0-alpha.0', + '@storybook/docs-tools': '8.1.0-alpha.0', + '@storybook/ember': '8.1.0-alpha.0', + '@storybook/html': '8.1.0-alpha.0', + '@storybook/html-vite': '8.1.0-alpha.0', + '@storybook/html-webpack5': '8.1.0-alpha.0', + '@storybook/instrumenter': '8.1.0-alpha.0', + '@storybook/manager': '8.1.0-alpha.0', + '@storybook/manager-api': '8.1.0-alpha.0', + '@storybook/nextjs': '8.1.0-alpha.0', + '@storybook/node-logger': '8.1.0-alpha.0', + '@storybook/preact': '8.1.0-alpha.0', + '@storybook/preact-vite': '8.1.0-alpha.0', + '@storybook/preact-webpack5': '8.1.0-alpha.0', + '@storybook/preset-create-react-app': '8.1.0-alpha.0', + '@storybook/preset-html-webpack': '8.1.0-alpha.0', + '@storybook/preset-preact-webpack': '8.1.0-alpha.0', + '@storybook/preset-react-webpack': '8.1.0-alpha.0', + '@storybook/preset-server-webpack': '8.1.0-alpha.0', + '@storybook/preset-svelte-webpack': '8.1.0-alpha.0', + '@storybook/preset-vue3-webpack': '8.1.0-alpha.0', + '@storybook/preview': '8.1.0-alpha.0', + '@storybook/preview-api': '8.1.0-alpha.0', + '@storybook/react': '8.1.0-alpha.0', + '@storybook/react-dom-shim': '8.1.0-alpha.0', + '@storybook/react-vite': '8.1.0-alpha.0', + '@storybook/react-webpack5': '8.1.0-alpha.0', + '@storybook/router': '8.1.0-alpha.0', + '@storybook/server': '8.1.0-alpha.0', + '@storybook/server-webpack5': '8.1.0-alpha.0', + '@storybook/source-loader': '8.1.0-alpha.0', + '@storybook/svelte': '8.1.0-alpha.0', + '@storybook/svelte-vite': '8.1.0-alpha.0', + '@storybook/svelte-webpack5': '8.1.0-alpha.0', + '@storybook/sveltekit': '8.1.0-alpha.0', + '@storybook/telemetry': '8.1.0-alpha.0', + '@storybook/test': '8.1.0-alpha.0', + '@storybook/theming': '8.1.0-alpha.0', + '@storybook/types': '8.1.0-alpha.0', + '@storybook/vue3': '8.1.0-alpha.0', + '@storybook/vue3-vite': '8.1.0-alpha.0', + '@storybook/vue3-webpack5': '8.1.0-alpha.0', + '@storybook/web-components': '8.1.0-alpha.0', + '@storybook/web-components-vite': '8.1.0-alpha.0', + '@storybook/web-components-webpack5': '8.1.0-alpha.0', + sb: '8.1.0-alpha.0', + storybook: '8.1.0-alpha.0', }; diff --git a/code/lib/core-events/package.json b/code/lib/core-events/package.json index 03a63f346e27..2fda3e0eb626 100644 --- a/code/lib/core-events/package.json +++ b/code/lib/core-events/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-events", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Event names used in storybook core", "keywords": [ "storybook" diff --git a/code/lib/core-events/src/index.ts b/code/lib/core-events/src/index.ts index 6d98e6291200..7542d0aa4e57 100644 --- a/code/lib/core-events/src/index.ts +++ b/code/lib/core-events/src/index.ts @@ -1,5 +1,6 @@ // eslint-disable-next-line @typescript-eslint/naming-convention enum events { + CHANNEL_WS_DISCONNECT = 'channelWSDisconnect', CHANNEL_CREATED = 'channelCreated', // There was an error executing the config, likely an bug in the user's preview.js CONFIG_ERROR = 'configError', @@ -80,6 +81,7 @@ export default events; // Enables: `import * as Events from ...` or `import { CHANNEL_CREATED } as Events from ...` // This is the preferred method export const { + CHANNEL_WS_DISCONNECT, CHANNEL_CREATED, CONFIG_ERROR, CURRENT_STORY_WAS_SET, diff --git a/code/lib/core-server/package.json b/code/lib/core-server/package.json index 663facee0635..2b44e68b8a22 100644 --- a/code/lib/core-server/package.json +++ b/code/lib/core-server/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-server", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/lib/core-webpack/package.json b/code/lib/core-webpack/package.json index 46f38e226ea1..d1e6142601ea 100644 --- a/code/lib/core-webpack/package.json +++ b/code/lib/core-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-webpack", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/lib/csf-plugin/package.json b/code/lib/csf-plugin/package.json index c447831f6657..1761eb435ae1 100644 --- a/code/lib/csf-plugin/package.json +++ b/code/lib/csf-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/csf-plugin", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Enrich CSF files via static analysis", "keywords": [ "storybook" diff --git a/code/lib/csf-tools/package.json b/code/lib/csf-tools/package.json index 9004d3e3beae..f8e58033aff3 100644 --- a/code/lib/csf-tools/package.json +++ b/code/lib/csf-tools/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/csf-tools", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Parse and manipulate CSF and Storybook config files", "keywords": [ "storybook" diff --git a/code/lib/docs-tools/package.json b/code/lib/docs-tools/package.json index 3025df49aa29..2c926e64db68 100644 --- a/code/lib/docs-tools/package.json +++ b/code/lib/docs-tools/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/docs-tools", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Shared utility functions for frameworks to implement docs", "keywords": [ "storybook" diff --git a/code/lib/instrumenter/package.json b/code/lib/instrumenter/package.json index 32c7c06686a8..c411b7c9138c 100644 --- a/code/lib/instrumenter/package.json +++ b/code/lib/instrumenter/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/instrumenter", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "", "keywords": [ "storybook" diff --git a/code/lib/manager-api/package.json b/code/lib/manager-api/package.json index a790395da7a3..566511f21346 100644 --- a/code/lib/manager-api/package.json +++ b/code/lib/manager-api/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/manager-api", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Core Storybook Manager API & Context", "keywords": [ "storybook" @@ -65,7 +65,6 @@ "@types/qs": "^6", "@types/semver": "^7.3.4", "flush-promises": "^1.0.2", - "qs": "^6.10.0", "react": "^18.2.0", "react-dom": "^18.2.0", "semver": "^7.3.7", diff --git a/code/lib/manager-api/src/lib/events.ts b/code/lib/manager-api/src/lib/events.ts index c66c5a9e1403..1892d71ed57a 100644 --- a/code/lib/manager-api/src/lib/events.ts +++ b/code/lib/manager-api/src/lib/events.ts @@ -19,9 +19,13 @@ export const getEventMetadata = (context: Meta, fullAPI: API) => { const { source, refId, type } = context; const [sourceType, sourceLocation] = getSourceType(source!, refId); - const ref = - refId && fullAPI.getRefs()[refId] ? fullAPI.getRefs()[refId] : fullAPI.findRef(sourceLocation!); - + let ref: API_ComposedRef | undefined; + if (refId || sourceType === 'external') { + ref = + refId && fullAPI.getRefs()[refId] + ? fullAPI.getRefs()[refId] + : fullAPI.findRef(sourceLocation!); + } const meta = { source, sourceType, diff --git a/code/lib/manager-api/src/modules/refs.ts b/code/lib/manager-api/src/modules/refs.ts index 906798848e16..4a5fca881f48 100644 --- a/code/lib/manager-api/src/modules/refs.ts +++ b/code/lib/manager-api/src/modules/refs.ts @@ -20,8 +20,6 @@ import type { ModuleFn } from '../lib/types'; const { location, fetch } = global; -const findFilename = /(\/((?:[^\/]+?)\.[^\/]+?)|\/)$/; - export interface SubState { refs: API_Refs; } @@ -45,7 +43,7 @@ export interface SubAPI { * @param {string} id - The ID of the composed ref. * @param {API_ComposedRefUpdate} ref - The update object for the composed ref. */ - updateRef: (id: string, ref: API_ComposedRefUpdate) => void; + updateRef: (id: string, ref: API_ComposedRefUpdate) => Promise; /** * Gets all composed refs. * @returns {API_Refs} - The composed refs object. @@ -62,7 +60,7 @@ export interface SubAPI { * @param {string} id - The ID of the composed ref. * @param {string} url - The new URL for the composed ref. */ - changeRefVersion: (id: string, url: string) => void; + changeRefVersion: (id: string, url: string) => Promise; /** * Changes the state of a composed ref by its ID and previewInitialized flag. * @param {string} id - The ID of the composed ref. @@ -75,8 +73,10 @@ export const getSourceType = (source: string, refId?: string) => { const { origin: localOrigin, pathname: localPathname } = location; const { origin: sourceOrigin, pathname: sourcePathname } = new URL(source); - const localFull = `${localOrigin + localPathname}`.replace(findFilename, ''); - const sourceFull = `${sourceOrigin + sourcePathname}`.replace(findFilename, ''); + const localFull = `${localOrigin + localPathname}`.replace('/iframe.html', '').replace(/\/$/, ''); + const sourceFull = `${sourceOrigin + sourcePathname}` + .replace('/iframe.html', '') + .replace(/\/$/, ''); if (localFull === sourceFull) { return ['local', sourceFull]; @@ -168,12 +168,12 @@ export const init: ModuleFn = ( return Object.values(refs).find(({ url }: any) => url.match(source)); }, - changeRefVersion: (id, url) => { + changeRefVersion: async (id, url) => { const { versions, title } = api.getRefs()[id]; const ref: API_SetRefData = { id, url, versions, title, index: {}, expanded: true }; - api.setRef(id, { ...ref, type: 'unknown' }, false); - api.checkRef(ref); + await api.setRef(id, { ...ref, type: 'unknown' }, false); + await api.checkRef(ref); }, changeRefState: (id, previewInitialized) => { const { [id]: ref, ...updated } = api.getRefs(); @@ -276,7 +276,7 @@ export const init: ModuleFn = ( return refs; }, - setRef: (id, { storyIndex, setStoriesData, ...rest }, ready = false) => { + setRef: async (id, { storyIndex, setStoriesData, ...rest }, ready = false) => { if (singleStory) { return; } @@ -307,10 +307,10 @@ export const init: ModuleFn = ( index = addRefIds(index, ref); } - api.updateRef(id, { ...ref, ...rest, index, internal_index }); + await api.updateRef(id, { ...ref, ...rest, index, internal_index }); }, - updateRef: (id, data) => { + updateRef: async (id, data) => { const { [id]: ref, ...updated } = api.getRefs(); updated[id] = { ...ref, ...data }; @@ -320,7 +320,7 @@ export const init: ModuleFn = ( return obj; }, {}); - store.setState({ + await store.setState({ refs: ordered, }); }, @@ -331,8 +331,11 @@ export const init: ModuleFn = ( const initialState: SubState['refs'] = refs; if (runCheck) { - Object.entries(refs).forEach(([id, ref]) => { - api.checkRef({ ...ref!, stories: {} } as API_SetRefData); + new Promise(async (resolve) => { + for (const ref of Object.values(refs)) { + await api.checkRef({ ...ref!, stories: {} } as API_SetRefData); + } + resolve(undefined); }); } diff --git a/code/lib/manager-api/src/modules/whatsnew.ts b/code/lib/manager-api/src/modules/whatsnew.ts index eeeb9558a59f..8f465325d503 100644 --- a/code/lib/manager-api/src/modules/whatsnew.ts +++ b/code/lib/manager-api/src/modules/whatsnew.ts @@ -92,10 +92,10 @@ export const init: ModuleFn = ({ fullAPI, store, provider }) => { id: WHATS_NEW_NOTIFICATION_ID, link: '/settings/whats-new', content: { - headline: whatsNewData.excerpt, - subHeadline: "Click to learn what's new in Storybook", + headline: whatsNewData.title, + subHeadline: "Learn what's new in Storybook", }, - icon: { name: 'hearthollow' }, + icon: { name: 'storybook' }, onClear({ dismissed }: any) { if (dismissed) { setWhatsNewCache({ lastDismissedPost: whatsNewData.url }); diff --git a/code/lib/manager-api/src/tests/refs.test.ts b/code/lib/manager-api/src/tests/refs.test.ts index afdb0747e39e..950b5e3e7a63 100644 --- a/code/lib/manager-api/src/tests/refs.test.ts +++ b/code/lib/manager-api/src/tests/refs.test.ts @@ -25,7 +25,7 @@ vi.mock('@storybook/global', () => { // Add additional variations of global.location mock return values in this array. // NOTE: The order must match the order that global.location is called in the unit tests. const edgecaseLocations = [ - { origin: 'https://storybook.js.org', pathname: '/storybook/index.html' }, + { origin: 'https://storybook.js.org', pathname: '/storybook/iframe.html' }, ]; // global.location value after all edgecaseLocations are returned const lastLocation = { origin: 'https://storybook.js.org', pathname: '/storybook/' }; @@ -171,6 +171,9 @@ describe('Refs API', () => { // given initRefs({ provider, store } as any); + // the `runCheck` is async, so we need to wait for it to finish + await vi.waitFor(() => fetchMock.mock.calls.length > 0); + expect(fetchMock.mock.calls).toMatchInlineSnapshot(` [ [ @@ -207,6 +210,9 @@ describe('Refs API', () => { }; initRefs({ provider, store } as any); + // the `runCheck` is async, so we need to wait for it to finish + await vi.waitFor(() => fetchMock.mock.calls.length > 0); + expect(fetchMock.mock.calls).toMatchInlineSnapshot(` [ [ diff --git a/code/lib/manager-api/src/tests/url.test.js b/code/lib/manager-api/src/tests/url.test.js index 02048e903c37..817cb118ff41 100644 --- a/code/lib/manager-api/src/tests/url.test.js +++ b/code/lib/manager-api/src/tests/url.test.js @@ -1,5 +1,4 @@ import { describe, beforeEach, it, expect, vi } from 'vitest'; -import qs from 'qs'; import { SET_CURRENT_STORY, GLOBALS_UPDATED, UPDATE_QUERY_PARAMS } from '@storybook/core-events'; @@ -15,7 +14,7 @@ describe('initial state', () => { describe('config query parameters', () => { it('handles full parameter', () => { const navigate = vi.fn(); - const location = { search: qs.stringify({ full: '1' }) }; + const location = { search: new URLSearchParams({ full: '1' }).toString() }; const { state: { layout }, @@ -30,7 +29,7 @@ describe('initial state', () => { it('handles nav parameter', () => { const navigate = vi.fn(); - const location = { search: qs.stringify({ nav: '0' }) }; + const location = { search: new URLSearchParams({ nav: '0' }).toString() }; const { state: { layout }, @@ -41,7 +40,7 @@ describe('initial state', () => { it('handles shortcuts parameter', () => { const navigate = vi.fn(); - const location = { search: qs.stringify({ shortcuts: '0' }) }; + const location = { search: new URLSearchParams({ shortcuts: '0' }).toString() }; const { state: { ui }, @@ -52,7 +51,7 @@ describe('initial state', () => { it('handles panel parameter, bottom', () => { const navigate = vi.fn(); - const location = { search: qs.stringify({ panel: 'bottom' }) }; + const location = { search: new URLSearchParams({ panel: 'bottom' }).toString() }; const { state: { layout }, @@ -63,7 +62,7 @@ describe('initial state', () => { it('handles panel parameter, right', () => { const navigate = vi.fn(); - const location = { search: qs.stringify({ panel: 'right' }) }; + const location = { search: new URLSearchParams({ panel: 'right' }).toString() }; const { state: { layout }, @@ -74,7 +73,7 @@ describe('initial state', () => { it('handles panel parameter, 0', () => { const navigate = vi.fn(); - const location = { search: qs.stringify({ panel: '0' }) }; + const location = { search: new URLSearchParams({ panel: '0' }).toString() }; const { state: { layout }, diff --git a/code/lib/manager-api/src/version.ts b/code/lib/manager-api/src/version.ts index 156fd9c6a94a..f89e3528d2df 100644 --- a/code/lib/manager-api/src/version.ts +++ b/code/lib/manager-api/src/version.ts @@ -1 +1 @@ -export const version = '8.0.0-rc.1'; +export const version = '8.1.0-alpha.0'; diff --git a/code/lib/node-logger/package.json b/code/lib/node-logger/package.json index 12672e9c0278..739cd0051874 100644 --- a/code/lib/node-logger/package.json +++ b/code/lib/node-logger/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/node-logger", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "", "keywords": [ "storybook" diff --git a/code/lib/preview-api/package.json b/code/lib/preview-api/package.json index 75d64c9a87b8..126be65d5e3a 100644 --- a/code/lib/preview-api/package.json +++ b/code/lib/preview-api/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preview-api", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "", "keywords": [ "storybook" diff --git a/code/lib/preview-api/src/index.ts b/code/lib/preview-api/src/index.ts index 63d45114dc23..e47cdaa0a0dd 100644 --- a/code/lib/preview-api/src/index.ts +++ b/code/lib/preview-api/src/index.ts @@ -56,7 +56,6 @@ export { filterArgTypes, sanitizeStoryContextUpdate, setProjectAnnotations, - getPortableStoryWrapperId, inferControls, userOrAutoTitleFromSpecifier, userOrAutoTitle, diff --git a/code/lib/preview-api/src/modules/store/csf/portable-stories.ts b/code/lib/preview-api/src/modules/store/csf/portable-stories.ts index 0974f0908526..bee091bfda4b 100644 --- a/code/lib/preview-api/src/modules/store/csf/portable-stories.ts +++ b/code/lib/preview-api/src/modules/store/csf/portable-stories.ts @@ -26,10 +26,6 @@ import { normalizeProjectAnnotations } from './normalizeProjectAnnotations'; let globalProjectAnnotations: ProjectAnnotations = {}; -export function getPortableStoryWrapperId(storyId: string) { - return `storybook-story-${storyId}`; -} - export function setProjectAnnotations( projectAnnotations: ProjectAnnotations | ProjectAnnotations[] ) { @@ -99,11 +95,7 @@ export function composeStory = { layout: 'fullscreen', }, // More on argTypes: https://storybook.js.org/docs/api/argtypes - argTypes: { - onLogin: { action: 'onLogin' }, - onLogout: { action: 'onLogout' }, - onCreateAccount: { action: 'onCreateAccount' }, + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), }, }; diff --git a/code/renderers/html/template/cli/ts-4-9/Header.stories.ts b/code/renderers/html/template/cli/ts-4-9/Header.stories.ts index 7570a625a869..189c6c8abebd 100644 --- a/code/renderers/html/template/cli/ts-4-9/Header.stories.ts +++ b/code/renderers/html/template/cli/ts-4-9/Header.stories.ts @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from '@storybook/html'; +import { fn } from '@storybook/test'; import type { HeaderProps } from './Header'; import { createHeader } from './Header'; @@ -12,10 +13,10 @@ const meta = { layout: 'fullscreen', }, // More on argTypes: https://storybook.js.org/docs/api/argtypes - argTypes: { - onLogin: { action: 'onLogin' }, - onLogout: { action: 'onLogout' }, - onCreateAccount: { action: 'onCreateAccount' }, + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), }, } satisfies Meta; diff --git a/code/renderers/preact/package.json b/code/renderers/preact/package.json index 685eb8e50aaa..fe1df6ad774b 100644 --- a/code/renderers/preact/package.json +++ b/code/renderers/preact/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook Preact renderer", "keywords": [ "storybook" diff --git a/code/renderers/preact/template/cli/Header.stories.jsx b/code/renderers/preact/template/cli/Header.stories.jsx index 58b353a57db2..160f7fdff166 100644 --- a/code/renderers/preact/template/cli/Header.stories.jsx +++ b/code/renderers/preact/template/cli/Header.stories.jsx @@ -1,3 +1,4 @@ +import { fn } from '@storybook/test'; import { Header } from './Header'; export default { @@ -9,10 +10,10 @@ export default { // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout layout: 'fullscreen', }, - argTypes: { - onLogin: { action: 'onLogin' }, - onLogout: { action: 'onLogout' }, - onCreateAccount: { action: 'onCreateAccount' }, + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), }, }; diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json index de60e151b77c..6abc64587845 100644 --- a/code/renderers/react/package.json +++ b/code/renderers/react/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook React renderer", "keywords": [ "storybook" diff --git a/code/renderers/react/src/__test__/__snapshots__/portable-stories.test.tsx.snap b/code/renderers/react/src/__test__/__snapshots__/portable-stories.test.tsx.snap index 2779b21001ab..ce16b3dc224e 100644 --- a/code/renderers/react/src/__test__/__snapshots__/portable-stories.test.tsx.snap +++ b/code/renderers/react/src/__test__/__snapshots__/portable-stories.test.tsx.snap @@ -3,17 +3,12 @@ exports[`Renders CSF2Secondary story 1`] = `
-
- -
+ Children coming from story args! +
`; @@ -21,17 +16,12 @@ exports[`Renders CSF2Secondary story 1`] = ` exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = `
-
- -
+ foo +
`; @@ -39,17 +29,12 @@ exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = ` exports[`Renders CSF3Button story 1`] = `
-
- -
+ foo +
`; @@ -57,23 +42,18 @@ exports[`Renders CSF3Button story 1`] = ` exports[`Renders CSF3ButtonWithRender story 1`] = `
-
-
-

- I am a custom render function -

- -
+
+

+ I am a custom render function +

+
@@ -82,14 +62,9 @@ exports[`Renders CSF3ButtonWithRender story 1`] = ` exports[`Renders CSF3InputFieldFilled story 1`] = `
-
- -
+
`; @@ -97,17 +72,12 @@ exports[`Renders CSF3InputFieldFilled story 1`] = ` exports[`Renders CSF3Primary story 1`] = `
-
- -
+ foo +
`; @@ -115,21 +85,16 @@ exports[`Renders CSF3Primary story 1`] = ` exports[`Renders LoaderStory story 1`] = `
-
-
-
- loaded data -
-
- mockFn return value -
+
+
+ loaded data +
+
+ mockFn return value
diff --git a/code/renderers/react/src/docs/jsxDecorator.test.tsx b/code/renderers/react/src/docs/jsxDecorator.test.tsx index bfb20fdd5f0d..6ed0f0eda179 100644 --- a/code/renderers/react/src/docs/jsxDecorator.test.tsx +++ b/code/renderers/react/src/docs/jsxDecorator.test.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-underscore-dangle */ import type { FC, PropsWithChildren } from 'react'; import React, { StrictMode, createElement, Profiler } from 'react'; import type { Mock } from 'vitest'; @@ -5,7 +6,7 @@ import { vi, describe, it, expect, beforeEach } from 'vitest'; import PropTypes from 'prop-types'; import { addons, useEffect } from '@storybook/preview-api'; import { SNIPPET_RENDERED } from '@storybook/docs-tools'; -import { renderJsx, jsxDecorator } from './jsxDecorator'; +import { renderJsx, jsxDecorator, getReactSymbolName } from './jsxDecorator'; vi.mock('@storybook/preview-api'); const mockedAddons = vi.mocked(addons); @@ -16,6 +17,18 @@ expect.addSnapshotSerializer({ test: (val) => typeof val === 'string', }); +describe('converts React Symbol to displayName string', () => { + const symbolCases = [ + ['react.suspense', 'React.Suspense'], + ['react.strict_mode', 'React.StrictMode'], + ['react.server_context.defaultValue', 'React.ServerContext.DefaultValue'], + ]; + + it.each(symbolCases)('"%s" to "%s"', (symbol, expectedValue) => { + expect(getReactSymbolName(Symbol(symbol))).toEqual(expectedValue); + }); +}); + describe('renderJsx', () => { it('basic', () => { expect(renderJsx(
hello
, {})).toMatchInlineSnapshot(` @@ -139,53 +152,71 @@ describe('renderJsx', () => { }); it('Profiler', () => { - function ProfilerComponent({ children }: any) { - return ( + expect( + renderJsx( {}}> -
{children}
-
- ); - } - - expect(renderJsx(createElement(ProfilerComponent, {}, 'I am Profiler'), {})) - .toMatchInlineSnapshot(` - - I am Profiler - +
I am in a Profiler
+ , + {} + ) + ).toMatchInlineSnapshot(` + {}} + > +
+ I am in a Profiler +
+
`); }); it('StrictMode', () => { - function StrictModeComponent({ children }: any) { - return ( - -
{children}
-
- ); + expect(renderJsx(I am StrictMode, {})).toMatchInlineSnapshot(` + + I am StrictMode + + `); + }); + + it('displayName coming from docgenInfo', () => { + function BasicComponent({ label }: any) { + return ; } + BasicComponent.__docgenInfo = { + description: 'Some description', + methods: [], + displayName: 'Button', + props: {}, + }; - expect(renderJsx(createElement(StrictModeComponent, {}, 'I am StrictMode'), {})) - .toMatchInlineSnapshot(` - - I am StrictMode - - `); + expect( + renderJsx( + createElement( + BasicComponent, + { + label:

Abcd

, + }, + undefined + ) + ) + ).toMatchInlineSnapshot(` -
+ label coming from story args! +
`; @@ -22,19 +17,14 @@ exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = `
-
- -
+ foo +
@@ -43,17 +33,12 @@ exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = ` exports[`Renders CSF3Button story 1`] = `
-
- -
+ foo +
`; @@ -61,23 +46,18 @@ exports[`Renders CSF3Button story 1`] = ` exports[`Renders CSF3ButtonWithRender story 1`] = `
-
-
-

- I am a custom render function -

- -
+
+

+ I am a custom render function +

+
@@ -86,14 +66,9 @@ exports[`Renders CSF3ButtonWithRender story 1`] = ` exports[`Renders CSF3InputFieldFilled story 1`] = `
-
- -
+
`; @@ -101,17 +76,12 @@ exports[`Renders CSF3InputFieldFilled story 1`] = ` exports[`Renders CSF3Primary story 1`] = `
-
- -
+ foo +
`; @@ -119,21 +89,16 @@ exports[`Renders CSF3Primary story 1`] = ` exports[`Renders LoaderStory story 1`] = `
-
-
-
- loaded data -
-
- mockFn return value -
+
+
+ loaded data +
+
+ mockFn return value
diff --git a/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts b/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts index 36aa7c2e9c56..7491a376e07c 100644 --- a/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts +++ b/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts @@ -17,34 +17,34 @@ const Secondary = composeStory(stories.CSF2Secondary, stories.default); describe('renders', () => { it('renders primary button', () => { - render(CSF3Primary({ label: 'Hello world' })); + render(CSF3Primary, { props: { label: 'Hello world' } }); const buttonElement = screen.getByText(/Hello world/i); expect(buttonElement).toBeInTheDocument(); }); it('reuses args from composed story', () => { - render(Secondary()); + render(Secondary); const buttonElement = screen.getByRole('button'); expect(buttonElement.textContent).toEqual(Secondary.args.label); }); it('myClickEvent handler is called', async () => { const myClickEventSpy = vi.fn(); - render(Secondary({ onMyClickEvent: myClickEventSpy })); + render(Secondary, { props: { onMyClickEvent: myClickEventSpy } }); const buttonElement = screen.getByRole('button'); buttonElement.click(); expect(myClickEventSpy).toHaveBeenCalled(); }); it('reuses args from composeStories', () => { - const { getByText } = render(CSF3Primary()); + const { getByText } = render(CSF3Primary); const buttonElement = getByText(/foo/i); expect(buttonElement).toBeInTheDocument(); }); it('should call and compose loaders data', async () => { await LoaderStory.load(); - const { getByTestId } = render(LoaderStory()); + const { getByTestId } = render(LoaderStory); expect(getByTestId('spy-data').textContent).toEqual('mockFn return value'); expect(getByTestId('loaded-data').textContent).toEqual('loaded data'); // spy assertions happen in the play function and should work @@ -63,7 +63,7 @@ describe('projectAnnotations', () => { }, ]); const WithEnglishText = composeStory(stories.CSF2StoryWithLocale, stories.default); - const { getByText } = render(WithEnglishText()); + const { getByText } = render(WithEnglishText); const buttonElement = getByText('Hello!'); expect(buttonElement).toBeInTheDocument(); }); @@ -72,7 +72,7 @@ describe('projectAnnotations', () => { const WithPortugueseText = composeStory(stories.CSF2StoryWithLocale, stories.default, { globals: { locale: 'pt' }, }); - const { getByText } = render(WithPortugueseText()); + const { getByText } = render(WithPortugueseText); const buttonElement = getByText('OlΓ‘!'); expect(buttonElement).toBeInTheDocument(); }); @@ -88,7 +88,7 @@ describe('CSF3', () => { it('renders with inferred globalRender', () => { const Primary = composeStory(stories.CSF3Button, stories.default); - render(Primary({ label: 'Hello world' })); + render(Primary, { props: { label: 'Hello world' } }); const buttonElement = screen.getByText(/Hello world/i); expect(buttonElement).toBeInTheDocument(); }); @@ -96,16 +96,16 @@ describe('CSF3', () => { it('renders with custom render function', () => { const Primary = composeStory(stories.CSF3ButtonWithRender, stories.default); - render(Primary()); + render(Primary); expect(screen.getByTestId('custom-render')).toBeInTheDocument(); }); it('renders with play function', async () => { const CSF3InputFieldFilled = composeStory(stories.CSF3InputFieldFilled, stories.default); - const { container } = render(CSF3InputFieldFilled()); + render(CSF3InputFieldFilled); - await CSF3InputFieldFilled.play!({ canvasElement: container as HTMLElement }); + await CSF3InputFieldFilled.play!(); const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); @@ -122,7 +122,7 @@ it('should pass with decorators that need addons channel', () => { }, ], }); - render(PrimaryWithChannels({ label: 'Hello world' })); + render(PrimaryWithChannels, { props: { label: 'Hello world' } }); const buttonElement = screen.getByText(/Hello world/i); expect(buttonElement).not.toBeNull(); }); @@ -151,8 +151,8 @@ it.each(testCases)('Renders %s story', async (_storyName, Story) => { } await Story.load(); - const { container, baseElement } = await render(Story()); - await Story.play?.({ canvasElement: container as HTMLElement }); + const { baseElement } = await render(Story); + await Story.play?.(); await new Promise((resolve) => setTimeout(resolve, 0)); expect(baseElement).toMatchSnapshot(); diff --git a/code/renderers/vue3/src/portable-stories.ts b/code/renderers/vue3/src/portable-stories.ts index aef26b39a5e7..dca738d205bf 100644 --- a/code/renderers/vue3/src/portable-stories.ts +++ b/code/renderers/vue3/src/portable-stories.ts @@ -2,7 +2,6 @@ import { composeStory as originalComposeStory, composeStories as originalComposeStories, setProjectAnnotations as originalSetProjectAnnotations, - getPortableStoryWrapperId, } from '@storybook/preview-api'; import type { Args, @@ -11,25 +10,12 @@ import type { Store_CSFExports, StoriesWithPartialProps, } from '@storybook/types'; +import { h } from 'vue'; -import * as vueProjectAnnotations from './entry-preview'; +import * as defaultProjectAnnotations from './entry-preview'; import type { Meta } from './public-types'; import type { VueRenderer } from './types'; -const defaultProjectAnnotations: ProjectAnnotations = { - ...vueProjectAnnotations, - decorators: [ - function addStorybookId(story, { id }) { - return { - components: { story }, - template: `
- -
`, - }; - }, - ], -}; - /** Function that sets the globalConfig of your Storybook. The global config is the preview module of your .storybook folder. * * It should be run a single time, so that your global config (e.g. decorators) is applied to your stories when using `composeStories` or `composeStory`. @@ -68,7 +54,7 @@ export function setProjectAnnotations( * const Primary = composeStory(PrimaryStory, Meta); * * test('renders primary button with Hello World', () => { - * const { getByText } = render(Primary({label: "Hello world"})); + * const { getByText } = render(Primary, { props: { label: "Hello world" } }); * expect(getByText(/Hello world/i)).not.toBeNull(); * }); *``` @@ -84,13 +70,21 @@ export function composeStory( projectAnnotations?: ProjectAnnotations, exportsName?: string ) { - return originalComposeStory( + const composedStory = originalComposeStory( story as StoryAnnotationsOrFn, componentAnnotations, projectAnnotations, defaultProjectAnnotations, exportsName ); + + // Returning h(composedStory) instead makes it an actual Vue component renderable by @testing-library/vue, Playwright CT, etc. + const renderable = (...args: Parameters) => h(composedStory(...args)); + Object.assign(renderable, composedStory); + + // typing this as newable means TS allows it to be used as a JSX element + // TODO: we should do the same for composeStories as well + return renderable as unknown as typeof composedStory & { new (...args: any[]): any }; } /** @@ -110,7 +104,7 @@ export function composeStory( * const { Primary, Secondary } = composeStories(stories); * * test('renders primary button with Hello World', () => { - * const { getByText } = render(Primary({label: "Hello world"})); + * const { getByText } = render(Primary, { props: { label: "Hello world" } }); * expect(getByText(/Hello world/i)).not.toBeNull(); * }); *``` diff --git a/code/renderers/vue3/template/cli/js/Header.stories.js b/code/renderers/vue3/template/cli/js/Header.stories.js index 380492e24b24..6b0d1821f117 100644 --- a/code/renderers/vue3/template/cli/js/Header.stories.js +++ b/code/renderers/vue3/template/cli/js/Header.stories.js @@ -1,3 +1,4 @@ +import { fn } from '@storybook/test'; import MyHeader from './Header.vue'; export default { @@ -24,6 +25,11 @@ export default { // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout layout: 'fullscreen', }, + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), + }, }; export const LoggedIn = { diff --git a/code/renderers/vue3/template/cli/ts-3-8/Header.stories.ts b/code/renderers/vue3/template/cli/ts-3-8/Header.stories.ts index fd0ba06e9c5d..4982d99d85f3 100644 --- a/code/renderers/vue3/template/cli/ts-3-8/Header.stories.ts +++ b/code/renderers/vue3/template/cli/ts-3-8/Header.stories.ts @@ -1,3 +1,4 @@ +import { fn } from '@storybook/test'; import type { Meta, StoryObj } from '@storybook/vue3'; import MyHeader from './Header.vue'; @@ -21,6 +22,11 @@ const meta: Meta = { layout: 'fullscreen', }, // This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/writing-docs/autodocs + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), + }, tags: ['autodocs'], }; diff --git a/code/renderers/vue3/template/cli/ts-4-9/Header.stories.ts b/code/renderers/vue3/template/cli/ts-4-9/Header.stories.ts index eb8fc1fcee56..350a5d22a48a 100644 --- a/code/renderers/vue3/template/cli/ts-4-9/Header.stories.ts +++ b/code/renderers/vue3/template/cli/ts-4-9/Header.stories.ts @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from '@storybook/vue3'; +import { fn } from '@storybook/test'; import MyHeader from './Header.vue'; @@ -20,6 +21,11 @@ const meta = { // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout layout: 'fullscreen', }, + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), + }, // This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/writing-docs/autodocs tags: ['autodocs'], } satisfies Meta; diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/GlobalSetup.stories.ts b/code/renderers/vue3/template/stories_vue3-vite-default-ts/GlobalSetup.stories.ts index 5e01135e20c0..725b0e65ee93 100644 --- a/code/renderers/vue3/template/stories_vue3-vite-default-ts/GlobalSetup.stories.ts +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/GlobalSetup.stories.ts @@ -1,6 +1,5 @@ -import { expect } from '@storybook/test'; import type { Meta, StoryObj } from '@storybook/vue3'; -import { within } from '@storybook/testing-library'; +import { within, expect } from '@storybook/test'; import { inject } from 'vue'; import GlobalSetup from './GlobalSetup.vue'; diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveArgs.stories.ts b/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveArgs.stories.ts index 536c962c2ee1..ff076fcca733 100644 --- a/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveArgs.stories.ts +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveArgs.stories.ts @@ -1,7 +1,6 @@ -import { expect } from '@storybook/test'; import { global as globalThis } from '@storybook/global'; import type { Meta, StoryObj, StoryFn } from '@storybook/vue3'; -import { within, userEvent } from '@storybook/testing-library'; +import { within, userEvent, expect } from '@storybook/test'; import { UPDATE_STORY_ARGS, STORY_ARGS_UPDATED, RESET_STORY_ARGS } from '@storybook/core-events'; import ReactiveArgs from './ReactiveArgs.vue'; diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveDecorators.stories.ts b/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveDecorators.stories.ts index 143cd1784559..d6a7e743d778 100644 --- a/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveDecorators.stories.ts +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveDecorators.stories.ts @@ -1,5 +1,5 @@ import { global as globalThis } from '@storybook/global'; -import { userEvent, within } from '@storybook/testing-library'; +import { userEvent, within } from '@storybook/test'; import type { Meta, StoryObj } from '@storybook/vue3'; import { h } from 'vue'; import { RESET_STORY_ARGS, STORY_ARGS_UPDATED, UPDATE_STORY_ARGS } from '@storybook/core-events'; diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveSlots.stories.ts b/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveSlots.stories.ts index d0042b65a2a5..bde19efc07e8 100644 --- a/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveSlots.stories.ts +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveSlots.stories.ts @@ -1,6 +1,5 @@ -import { expect } from '@storybook/test'; import { global as globalThis } from '@storybook/global'; -import { within } from '@storybook/testing-library'; +import { within, expect } from '@storybook/test'; import { STORY_ARGS_UPDATED, RESET_STORY_ARGS, UPDATE_STORY_ARGS } from '@storybook/core-events'; import { h } from 'vue'; import type { Meta, StoryObj } from '@storybook/vue3'; diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/ScopedSlots.stories.ts b/code/renderers/vue3/template/stories_vue3-vite-default-ts/ScopedSlots.stories.ts index ef7a625ea413..b255be571d43 100644 --- a/code/renderers/vue3/template/stories_vue3-vite-default-ts/ScopedSlots.stories.ts +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/ScopedSlots.stories.ts @@ -1,7 +1,6 @@ -import { expect } from '@storybook/test'; import { global as globalThis } from '@storybook/global'; import type { Channel } from '@storybook/channels'; -import { within } from '@storybook/testing-library'; +import { within, expect } from '@storybook/test'; import { UPDATE_STORY_ARGS, STORY_ARGS_UPDATED, RESET_STORY_ARGS } from '@storybook/core-events'; import type { Meta, StoryObj } from '@storybook/vue3'; diff --git a/code/renderers/web-components/package.json b/code/renderers/web-components/package.json index fcd133473b18..73fe293ad3e3 100644 --- a/code/renderers/web-components/package.json +++ b/code/renderers/web-components/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook web-components renderer", "keywords": [ "lit", diff --git a/code/renderers/web-components/template/cli/js/Button.stories.js b/code/renderers/web-components/template/cli/js/Button.stories.js index d406b990ff7b..dfb4ad43ee74 100644 --- a/code/renderers/web-components/template/cli/js/Button.stories.js +++ b/code/renderers/web-components/template/cli/js/Button.stories.js @@ -1,3 +1,4 @@ +import { fn } from '@storybook/test'; import { Button } from './Button'; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories @@ -7,12 +8,12 @@ export default { render: (args) => Button(args), argTypes: { backgroundColor: { control: 'color' }, - onClick: { action: 'onClick' }, size: { control: { type: 'select' }, options: ['small', 'medium', 'large'], }, }, + args: { onClick: fn() }, }; // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args diff --git a/code/renderers/web-components/template/cli/js/Header.stories.js b/code/renderers/web-components/template/cli/js/Header.stories.js index d399cb869950..cfb94e39fc0c 100644 --- a/code/renderers/web-components/template/cli/js/Header.stories.js +++ b/code/renderers/web-components/template/cli/js/Header.stories.js @@ -1,3 +1,4 @@ +import { fn } from '@storybook/test'; import { Header } from './Header'; export default { @@ -5,8 +6,12 @@ export default { // This component will have an automatically generated Autodocs entry: https://storybook.js.org/web-components/vue/writing-docs/autodocs tags: ['autodocs'], render: (args) => Header(args), + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), + }, }; - export const LoggedIn = { args: { user: { diff --git a/code/renderers/web-components/template/cli/ts-3-8/Button.stories.ts b/code/renderers/web-components/template/cli/ts-3-8/Button.stories.ts index 62d561636779..e34857751666 100644 --- a/code/renderers/web-components/template/cli/ts-3-8/Button.stories.ts +++ b/code/renderers/web-components/template/cli/ts-3-8/Button.stories.ts @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from '@storybook/web-components'; +import { fn } from '@storybook/test'; import type { ButtonProps } from './Button'; import { Button } from './Button'; @@ -9,12 +10,12 @@ const meta: Meta = { render: (args) => Button(args), argTypes: { backgroundColor: { control: 'color' }, - onClick: { action: 'onClick' }, size: { control: { type: 'select' }, options: ['small', 'medium', 'large'], }, }, + args: { onClick: fn() }, }; export default meta; diff --git a/code/renderers/web-components/template/cli/ts-3-8/Header.stories.ts b/code/renderers/web-components/template/cli/ts-3-8/Header.stories.ts index aab89ba6a4b3..08b47d7692d8 100644 --- a/code/renderers/web-components/template/cli/ts-3-8/Header.stories.ts +++ b/code/renderers/web-components/template/cli/ts-3-8/Header.stories.ts @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from '@storybook/web-components'; +import { fn } from '@storybook/test'; import type { HeaderProps } from './Header'; import { Header } from './Header'; @@ -7,6 +8,11 @@ const meta: Meta = { // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs tags: ['autodocs'], render: (args: HeaderProps) => Header(args), + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), + }, }; export default meta; diff --git a/code/renderers/web-components/template/cli/ts-4-9/Button.stories.ts b/code/renderers/web-components/template/cli/ts-4-9/Button.stories.ts index 03516d6c1abf..52f3ae8ebd62 100644 --- a/code/renderers/web-components/template/cli/ts-4-9/Button.stories.ts +++ b/code/renderers/web-components/template/cli/ts-4-9/Button.stories.ts @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from '@storybook/web-components'; +import { fn } from '@storybook/test'; import type { ButtonProps } from './Button'; import { Button } from './Button'; @@ -9,12 +10,12 @@ const meta = { render: (args) => Button(args), argTypes: { backgroundColor: { control: 'color' }, - onClick: { action: 'onClick' }, size: { control: { type: 'select' }, options: ['small', 'medium', 'large'], }, }, + args: { onClick: fn() }, } satisfies Meta; export default meta; diff --git a/code/renderers/web-components/template/cli/ts-4-9/Header.stories.ts b/code/renderers/web-components/template/cli/ts-4-9/Header.stories.ts index 628e199db1f4..be13bf07cde4 100644 --- a/code/renderers/web-components/template/cli/ts-4-9/Header.stories.ts +++ b/code/renderers/web-components/template/cli/ts-4-9/Header.stories.ts @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from '@storybook/web-components'; +import { fn } from '@storybook/test'; import type { HeaderProps } from './Header'; import { Header } from './Header'; @@ -7,6 +8,11 @@ const meta = { // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs tags: ['autodocs'], render: (args: HeaderProps) => Header(args), + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), + }, } satisfies Meta; export default meta; diff --git a/code/ui/blocks/package.json b/code/ui/blocks/package.json index c48cbadc893a..c32ad8074761 100644 --- a/code/ui/blocks/package.json +++ b/code/ui/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/blocks", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Storybook Doc Blocks", "keywords": [ "storybook" diff --git a/code/ui/blocks/src/components/Story.stories.tsx b/code/ui/blocks/src/components/Story.stories.tsx index ae9a277a9425..fa1767d747af 100644 --- a/code/ui/blocks/src/components/Story.stories.tsx +++ b/code/ui/blocks/src/components/Story.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import type { Meta, ReactRenderer, StoryObj } from '@storybook/react'; -import { within } from '@storybook/testing-library'; +import { within } from '@storybook/test'; import type { PlayFunctionContext } from '@storybook/csf'; import type { WebRenderer, ModuleExport } from '@storybook/types'; import { RESET_STORY_ARGS, STORY_ARGS_UPDATED, UPDATE_STORY_ARGS } from '@storybook/core-events'; diff --git a/code/ui/blocks/src/controls/Boolean.stories.tsx b/code/ui/blocks/src/controls/Boolean.stories.tsx index e83338c41241..8f7c043701c6 100644 --- a/code/ui/blocks/src/controls/Boolean.stories.tsx +++ b/code/ui/blocks/src/controls/Boolean.stories.tsx @@ -1,6 +1,5 @@ -import { expect } from '@storybook/test'; import type { Meta, StoryObj } from '@storybook/react'; -import { within, fireEvent, waitFor } from '@storybook/testing-library'; +import { within, fireEvent, waitFor, expect } from '@storybook/test'; import { addons } from '@storybook/preview-api'; import { RESET_STORY_ARGS, STORY_ARGS_UPDATED } from '@storybook/core-events'; import { BooleanControl } from './Boolean'; diff --git a/code/ui/blocks/src/examples/Button.stories.tsx b/code/ui/blocks/src/examples/Button.stories.tsx index 7e22aef00064..d99917fdfee8 100644 --- a/code/ui/blocks/src/examples/Button.stories.tsx +++ b/code/ui/blocks/src/examples/Button.stories.tsx @@ -1,6 +1,5 @@ -import { expect } from '@storybook/test'; import type { Meta, StoryObj } from '@storybook/react'; -import { within, fireEvent } from '@storybook/testing-library'; +import { within, fireEvent, expect } from '@storybook/test'; import React from 'react'; import { Button } from './Button'; diff --git a/code/ui/components/package.json b/code/ui/components/package.json index 248ed68c73c5..0cb6cc2278d3 100644 --- a/code/ui/components/package.json +++ b/code/ui/components/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/components", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Core Storybook Components", "keywords": [ "storybook" diff --git a/code/ui/components/src/components/tabs/tabs.stories.tsx b/code/ui/components/src/components/tabs/tabs.stories.tsx index 9312c70c8bec..46a332a87f1f 100644 --- a/code/ui/components/src/components/tabs/tabs.stories.tsx +++ b/code/ui/components/src/components/tabs/tabs.stories.tsx @@ -2,14 +2,7 @@ import { expect } from '@storybook/test'; import React, { Fragment } from 'react'; import { action } from '@storybook/addon-actions'; import type { Meta, StoryObj } from '@storybook/react'; -import { - within, - fireEvent, - waitFor, - screen, - userEvent, - findByText, -} from '@storybook/testing-library'; +import { within, fireEvent, waitFor, screen, userEvent, findByText } from '@storybook/test'; import { CPUIcon, MemoryIcon } from '@storybook/icons'; import { Tabs, TabsState, TabWrapper } from './tabs'; import type { ChildrenList } from './tabs.helpers'; diff --git a/code/ui/manager/package.json b/code/ui/manager/package.json index b13135e4eb4f..1c2507505030 100644 --- a/code/ui/manager/package.json +++ b/code/ui/manager/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/manager", - "version": "8.0.0-rc.1", + "version": "8.1.0-alpha.0", "description": "Core Storybook UI", "keywords": [ "storybook" diff --git a/code/ui/manager/src/components/mobile/about/MobileAbout.stories.tsx b/code/ui/manager/src/components/mobile/about/MobileAbout.stories.tsx index 7ef6f9d89f92..b36e2e0854f7 100644 --- a/code/ui/manager/src/components/mobile/about/MobileAbout.stories.tsx +++ b/code/ui/manager/src/components/mobile/about/MobileAbout.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { ManagerContext } from '@storybook/manager-api'; import React, { useEffect } from 'react'; -import { within } from '@storybook/testing-library'; +import { within } from '@storybook/test'; import { MobileAbout } from './MobileAbout'; import { LayoutProvider, useLayout } from '../../layout/LayoutProvider'; diff --git a/code/ui/manager/src/components/mobile/navigation/MobileNavigation.stories.tsx b/code/ui/manager/src/components/mobile/navigation/MobileNavigation.stories.tsx index 7617574cdd4e..8978534f6890 100644 --- a/code/ui/manager/src/components/mobile/navigation/MobileNavigation.stories.tsx +++ b/code/ui/manager/src/components/mobile/navigation/MobileNavigation.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import React from 'react'; import { ManagerContext } from '@storybook/manager-api'; -import { within } from '@storybook/testing-library'; +import { within } from '@storybook/test'; import { startCase } from 'lodash'; import { MobileNavigation } from './MobileNavigation'; import { LayoutProvider, useLayout } from '../../layout/LayoutProvider'; diff --git a/code/ui/manager/src/components/sidebar/Menu.stories.tsx b/code/ui/manager/src/components/sidebar/Menu.stories.tsx index 98788518db4b..ca57b4780a67 100644 --- a/code/ui/manager/src/components/sidebar/Menu.stories.tsx +++ b/code/ui/manager/src/components/sidebar/Menu.stories.tsx @@ -1,11 +1,10 @@ import type { ComponentProps } from 'react'; import React from 'react'; -import { expect } from '@storybook/test'; import type { Meta, StoryObj } from '@storybook/react'; import { TooltipLinkList } from '@storybook/components'; import { styled } from '@storybook/theming'; -import { screen, userEvent, within } from '@storybook/testing-library'; +import { screen, userEvent, within, expect } from '@storybook/test'; import type { State } from '@storybook/manager-api'; import { LinkIcon } from '@storybook/icons'; import { SidebarMenu } from './Menu'; diff --git a/code/ui/manager/src/components/sidebar/Sidebar.stories.tsx b/code/ui/manager/src/components/sidebar/Sidebar.stories.tsx index 3e7b7e2b23f3..253775bc07c7 100644 --- a/code/ui/manager/src/components/sidebar/Sidebar.stories.tsx +++ b/code/ui/manager/src/components/sidebar/Sidebar.stories.tsx @@ -1,14 +1,14 @@ import React from 'react'; import type { IndexHash, State } from '@storybook/manager-api'; -import { types } from '@storybook/manager-api'; +import { ManagerContext, types } from '@storybook/manager-api'; import type { StoryObj, Meta } from '@storybook/react'; -import { within, userEvent } from '@storybook/testing-library'; +import { within, userEvent, expect } from '@storybook/test'; +import type { Addon_SidebarTopType } from '@storybook/types'; import { Button, IconButton } from '@storybook/components'; import { FaceHappyIcon } from '@storybook/icons'; import { Sidebar, DEFAULT_REF_ID } from './Sidebar'; import { standardData as standardHeaderData } from './Heading.stories'; -import * as ExplorerStories from './Explorer.stories'; import { mockDataset } from './mockdata'; import type { RefType } from './types'; import { LayoutProvider } from '../layout/LayoutProvider'; @@ -19,33 +19,62 @@ const wait = (ms: number) => setTimeout(resolve, ms); }); +const { menu } = standardHeaderData; +const index = mockDataset.withRoot as IndexHash; +const storyId = 'root-1-child-a2--grandchild-a1-1'; + +export const simpleData = { menu, index, storyId }; +export const loadingData = { menu }; + const meta = { component: Sidebar, title: 'Sidebar/Sidebar', excludeStories: /.*Data$/, parameters: { layout: 'fullscreen' }, + args: { + previewInitialized: true, + menu, + extra: [] as Addon_SidebarTopType[], + index: index, + storyId, + refId: DEFAULT_REF_ID, + refs: {}, + status: {}, + }, decorators: [ - ExplorerStories.default.decorators[0], (storyFn) => ( - - - {storyFn()} - + {}, + on: () => {}, + off: () => {}, + getShortcutKeys: () => ({ search: ['control', 'shift', 's'] }), + }, + } as any + } + > + + + {storyFn()} + + ), ], -} as Meta; +} satisfies Meta; export default meta; type Story = StoryObj; -const { menu } = standardHeaderData; -const index = mockDataset.withRoot as IndexHash; -const storyId = 'root-1-child-a2--grandchild-a1-1'; - -export const simpleData = { menu, index, storyId }; -export const loadingData = { menu }; - const refs: Record = { optimized: { id: 'optimized', @@ -57,6 +86,7 @@ const refs: Record = { }, }; +// eslint-disable-next-line local-rules/no-uncategorized-errors const indexError = new Error('Failed to load index'); const refsError = { @@ -75,146 +105,56 @@ const refsEmpty = { }, }; -export const Simple: Story = { - args: { previewInitialized: true }, - render: (args) => ( - - ), -}; +export const Simple: Story = {}; export const Loading: Story = { - args: { previewInitialized: false }, - render: (args) => ( - - ), + args: { + previewInitialized: false, + index: undefined, + }, }; export const Empty: Story = { args: { - previewInitialized: true, + index: {}, }, - render: (args) => ( - - ), }; export const IndexError: Story = { args: { - previewInitialized: true, + indexError, }, - render: (args) => ( - - ), }; export const WithRefs: Story = { args: { - previewInitialized: true, + refs, }, - render: (args) => ( - - ), }; export const LoadingWithRefs: Story = { args: { - previewInitialized: false, + ...Loading.args, + refs, }, - render: (args) => ( - - ), }; export const LoadingWithRefError: Story = { args: { - previewInitialized: false, + ...Loading.args, + refs: refsError, }, - render: (args) => ( - - ), }; export const WithRefEmpty: Story = { args: { - previewInitialized: true, + ...Empty.args, + refs: refsEmpty, }, - render: (args) => ( - - ), }; export const StatusesCollapsed: Story = { args: { - previewInitialized: true, status: Object.entries(index).reduce((acc, [id, item]) => { if (item.type !== 'story') { return acc; @@ -232,17 +172,6 @@ export const StatusesCollapsed: Story = { return acc; }, {}), }, - render: (args) => ( - - ), }; export const StatusesOpen: Story = { @@ -267,7 +196,7 @@ export const StatusesOpen: Story = { export const Searching: Story = { ...StatusesOpen, - parameters: { theme: 'light', chromatic: { delay: 2200 } }, + parameters: { chromatic: { delay: 2200 } }, play: async ({ canvasElement, step }) => { await step('wait 2000ms', () => wait(2000)); const canvas = await within(canvasElement); @@ -279,52 +208,92 @@ export const Searching: Story = { export const Bottom: Story = { args: { - previewInitialized: true, + bottom: [ + { + id: '1', + type: types.experimental_SIDEBAR_BOTTOM, + render: () => ( + + ), + }, + { + id: '2', + type: types.experimental_SIDEBAR_BOTTOM, + render: () => ( + + ), + }, + { + id: '3', + type: types.experimental_SIDEBAR_BOTTOM, + render: () => ( + + {' '} + + + ), + }, + ], + }, +}; + +/** + * Given the following sequence of events: + * 1. Story is selected at the top of the sidebar + * 2. The sidebar is scrolled to the bottom + * 3. Some re-rendering happens because of a changed state/prop + * The sidebar should remain scrolled to the bottom + */ +export const Scrolled: Story = { + parameters: { + // we need a very short viewport + viewport: { + defaultViewport: 'mobile1', + defaultOrientation: 'landscape', + }, + }, + args: { + storyId: 'group-1--child-b1', + }, + render: (args) => { + const [, setState] = React.useState(0); + return ( + <> + + + + ); + }, + play: async ({ canvasElement, step }) => { + const canvas = await within(canvasElement); + const scrollable = await canvasElement.querySelector('[data-radix-scroll-area-viewport]'); + await step('expand component', async () => { + const componentNode = await canvas.queryAllByText('Child A2')[1]; + userEvent.click(componentNode); + }); + await wait(100); + await step('scroll to bottom', async () => { + scrollable.scrollTo(0, scrollable.scrollHeight); + }); + await step('toggle parent state', async () => { + const button = await canvas.findByRole('button', { name: 'Change state' }); + button.click(); + }); + await wait(100); + + // expect the scrollable to be scrolled to the bottom + expect(scrollable.scrollTop).toBe(scrollable.scrollHeight - scrollable.clientHeight); }, - parameters: { theme: 'light' }, - render: (args) => ( - ( - - ), - }, - { - id: '2', - type: types.experimental_SIDEBAR_BOTTOM, - render: () => ( - - ), - }, - { - id: '3', - type: types.experimental_SIDEBAR_BOTTOM, - render: () => ( - - {' '} - - - ), - }, - ]} - /> - ), }; diff --git a/code/ui/manager/src/components/sidebar/Sidebar.tsx b/code/ui/manager/src/components/sidebar/Sidebar.tsx index 3ea23f6b48a5..f881e3d68d4d 100644 --- a/code/ui/manager/src/components/sidebar/Sidebar.tsx +++ b/code/ui/manager/src/components/sidebar/Sidebar.tsx @@ -17,7 +17,7 @@ import { Explorer } from './Explorer'; import { Search } from './Search'; import { SearchResults } from './SearchResults'; -import type { Refs, CombinedDataset, Selection } from './types'; +import type { CombinedDataset, Selection } from './types'; import { useLastViewed } from './useLastViewed'; import { MEDIA_DESKTOP_BREAKPOINT } from '../../constants'; @@ -79,20 +79,26 @@ const Swap = React.memo(function Swap({ }); const useCombination = ( - defaultRefData: API_LoadedRefData & { status: State['status'] }, - refs: Refs + index: SidebarProps['index'], + indexError: SidebarProps['indexError'], + previewInitialized: SidebarProps['previewInitialized'], + status: SidebarProps['status'], + refs: SidebarProps['refs'] ): CombinedDataset => { const hash = useMemo( () => ({ [DEFAULT_REF_ID]: { - ...defaultRefData, + index, + indexError, + previewInitialized, + status, title: null, id: DEFAULT_REF_ID, url: 'iframe.html', }, ...refs, }), - [refs, defaultRefData] + [refs, index, indexError, previewInitialized, status] ); return useMemo(() => ({ hash, entries: Object.entries(hash) }), [hash]); }; @@ -126,7 +132,7 @@ export const Sidebar = React.memo(function Sidebar({ onMenuClick, }: SidebarProps) { const selected: Selection = useMemo(() => storyId && { storyId, refId }, [storyId, refId]); - const dataset = useCombination({ index, indexError, previewInitialized, status }, refs); + const dataset = useCombination(index, indexError, previewInitialized, status, refs); const isLoading = !index && !indexError; const lastViewedProps = useLastViewed(selected); diff --git a/code/ui/manager/src/components/sidebar/Tree.stories.tsx b/code/ui/manager/src/components/sidebar/Tree.stories.tsx index eb2aa83959fc..00036a574db5 100644 --- a/code/ui/manager/src/components/sidebar/Tree.stories.tsx +++ b/code/ui/manager/src/components/sidebar/Tree.stories.tsx @@ -4,9 +4,7 @@ import type { ComponentEntry, IndexHash } from '@storybook/manager-api'; import { action } from '@storybook/addon-actions'; import type { StoryObj, Meta } from '@storybook/react'; -import { within } from '@storybook/testing-library'; - -import { expect } from '@storybook/test'; +import { within, expect } from '@storybook/test'; import { Tree } from './Tree'; import { index } from './mockdata.large'; import { DEFAULT_REF_ID } from './Sidebar'; diff --git a/code/ui/manager/src/globals/exports.ts b/code/ui/manager/src/globals/exports.ts index b8e2d5b14868..079340369fcb 100644 --- a/code/ui/manager/src/globals/exports.ts +++ b/code/ui/manager/src/globals/exports.ts @@ -130,6 +130,7 @@ export default { ], '@storybook/core-events': [ 'CHANNEL_CREATED', + 'CHANNEL_WS_DISCONNECT', 'CONFIG_ERROR', 'CURRENT_STORY_WAS_SET', 'DOCS_PREPARED', diff --git a/code/yarn.lock b/code/yarn.lock index b9fe9698d8c4..ec1b24633a90 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -2583,156 +2583,163 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-arm64@npm:0.18.20" +"@esbuild/aix-ppc64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/aix-ppc64@npm:0.20.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/android-arm64@npm:0.20.1" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-arm@npm:0.18.20" +"@esbuild/android-arm@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/android-arm@npm:0.20.1" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-x64@npm:0.18.20" +"@esbuild/android-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/android-x64@npm:0.20.1" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/darwin-arm64@npm:0.18.20" +"@esbuild/darwin-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/darwin-arm64@npm:0.20.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/darwin-x64@npm:0.18.20" +"@esbuild/darwin-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/darwin-x64@npm:0.20.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/freebsd-arm64@npm:0.18.20" +"@esbuild/freebsd-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/freebsd-arm64@npm:0.20.1" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/freebsd-x64@npm:0.18.20" +"@esbuild/freebsd-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/freebsd-x64@npm:0.20.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-arm64@npm:0.18.20" +"@esbuild/linux-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-arm64@npm:0.20.1" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-arm@npm:0.18.20" +"@esbuild/linux-arm@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-arm@npm:0.20.1" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-ia32@npm:0.18.20" +"@esbuild/linux-ia32@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-ia32@npm:0.20.1" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-loong64@npm:0.18.20" +"@esbuild/linux-loong64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-loong64@npm:0.20.1" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-mips64el@npm:0.18.20" +"@esbuild/linux-mips64el@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-mips64el@npm:0.20.1" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-ppc64@npm:0.18.20" +"@esbuild/linux-ppc64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-ppc64@npm:0.20.1" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-riscv64@npm:0.18.20" +"@esbuild/linux-riscv64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-riscv64@npm:0.20.1" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-s390x@npm:0.18.20" +"@esbuild/linux-s390x@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-s390x@npm:0.20.1" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-x64@npm:0.18.20" +"@esbuild/linux-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-x64@npm:0.20.1" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/netbsd-x64@npm:0.18.20" +"@esbuild/netbsd-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/netbsd-x64@npm:0.20.1" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/openbsd-x64@npm:0.18.20" +"@esbuild/openbsd-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/openbsd-x64@npm:0.20.1" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/sunos-x64@npm:0.18.20" +"@esbuild/sunos-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/sunos-x64@npm:0.20.1" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-arm64@npm:0.18.20" +"@esbuild/win32-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/win32-arm64@npm:0.20.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-ia32@npm:0.18.20" +"@esbuild/win32-ia32@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/win32-ia32@npm:0.20.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-x64@npm:0.18.20" +"@esbuild/win32-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/win32-x64@npm:0.20.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -5114,7 +5121,7 @@ __metadata: "@storybook/instrumenter": "workspace:*" "@storybook/manager-api": "workspace:*" "@storybook/preview-api": "workspace:*" - "@storybook/testing-library": "npm:next" + "@storybook/test": "workspace:*" "@storybook/theming": "workspace:*" "@storybook/types": "workspace:*" "@types/node": "npm:^18.0.0" @@ -5216,7 +5223,6 @@ __metadata: "@storybook/react": "workspace:*" "@storybook/telemetry": "workspace:*" "@storybook/test": "workspace:*" - "@storybook/testing-library": "npm:next" "@storybook/theming": "workspace:*" "@storybook/types": "workspace:*" framer-motion: "npm:^11.0.3" @@ -5470,7 +5476,7 @@ __metadata: "@yarnpkg/esbuild-plugin-pnp": "npm:^3.0.0-rc.10" browser-assert: "npm:^1.2.1" ejs: "npm:^3.1.8" - esbuild: "npm:^0.18.0" + esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0" esbuild-plugin-alias: "npm:^0.2.1" express: "npm:^4.17.3" fs-extra: "npm:^11.1.0" @@ -5581,7 +5587,6 @@ __metadata: "@storybook/client-logger": "workspace:*" "@storybook/core-events": "workspace:*" "@storybook/global": "npm:^5.0.0" - qs: "npm:^6.10.0" telejson: "npm:^7.2.0" tiny-invariant: "npm:^1.3.1" typescript: "npm:^5.3.2" @@ -5737,7 +5742,7 @@ __metadata: "@yarnpkg/libzip": "npm:2.3.0" chalk: "npm:^4.1.0" cross-spawn: "npm:^7.0.3" - esbuild: "npm:^0.18.0" + esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0" esbuild-register: "npm:^3.5.0" execa: "npm:^5.0.0" file-system-cache: "npm:2.3.0" @@ -6101,7 +6106,6 @@ __metadata: flush-promises: "npm:^1.0.2" lodash: "npm:^4.17.21" memoizerific: "npm:^1.11.3" - qs: "npm:^6.10.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" semver: "npm:^7.3.7" @@ -6674,7 +6678,7 @@ __metadata: concurrently: "npm:^5.3.0" cross-env: "npm:^7.0.3" danger: "npm:^11.2.6" - esbuild: "npm:^0.18.0" + esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0" esbuild-loader: "npm:^3.0.0" esbuild-plugin-alias: "npm:^0.2.1" eslint: "npm:^8.56.0" @@ -7085,7 +7089,7 @@ __metadata: languageName: node linkType: hard -"@sveltejs/vite-plugin-svelte-inspector@npm:^2.0.0, @sveltejs/vite-plugin-svelte-inspector@npm:^2.0.0-next.0 || ^2.0.0": +"@sveltejs/vite-plugin-svelte-inspector@npm:^2.0.0": version: 2.0.0 resolution: "@sveltejs/vite-plugin-svelte-inspector@npm:2.0.0" dependencies: @@ -7098,25 +7102,7 @@ __metadata: languageName: node linkType: hard -"@sveltejs/vite-plugin-svelte@npm:^3.0.1": - version: 3.0.1 - resolution: "@sveltejs/vite-plugin-svelte@npm:3.0.1" - dependencies: - "@sveltejs/vite-plugin-svelte-inspector": "npm:^2.0.0-next.0 || ^2.0.0" - debug: "npm:^4.3.4" - deepmerge: "npm:^4.3.1" - kleur: "npm:^4.1.5" - magic-string: "npm:^0.30.5" - svelte-hmr: "npm:^0.15.3" - vitefu: "npm:^0.2.5" - peerDependencies: - svelte: ^4.0.0 || ^5.0.0-next.0 - vite: ^5.0.0 - checksum: 889d41014d4cc5dfb578cb0a80e64f72c0f8c143e9a299c3a4e2372fd582d982ce118dad5e158e0b747d1df7354a909ed9490b1adcd1bf982b56c82fffd4652c - languageName: node - linkType: hard - -"@sveltejs/vite-plugin-svelte@npm:^3.0.2": +"@sveltejs/vite-plugin-svelte@npm:^3.0.1, @sveltejs/vite-plugin-svelte@npm:^3.0.2": version: 3.0.2 resolution: "@sveltejs/vite-plugin-svelte@npm:3.0.2" dependencies: @@ -7586,10 +7572,10 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.1": - version: 1.0.2 - resolution: "@types/estree@npm:1.0.2" - checksum: 4b5c601d435ea8e2205458de15fd1556b5ae6c9a8323bad8a940ea502d6c824664faca94234c0bf76bf9c87cbf6ac41abee550c9e20433256549d589c9b543bd +"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.1, @types/estree@npm:^1.0.5": + version: 1.0.5 + resolution: "@types/estree@npm:1.0.5" + checksum: b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d languageName: node linkType: hard @@ -7600,13 +7586,6 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:^1.0.5": - version: 1.0.5 - resolution: "@types/estree@npm:1.0.5" - checksum: b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d - languageName: node - linkType: hard - "@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.33": version: 4.17.37 resolution: "@types/express-serve-static-core@npm:4.17.37" @@ -9549,16 +9528,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.0.0, acorn@npm:^8.10.0, acorn@npm:^8.11.2, acorn@npm:^8.4.1, acorn@npm:^8.6.0, acorn@npm:^8.7.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": - version: 8.11.2 - resolution: "acorn@npm:8.11.2" - bin: - acorn: bin/acorn - checksum: a3ed76c761b75ec54b1ec3068fb7f113a182e95aea7f322f65098c2958d232e3d211cb6dac35ff9c647024b63714bc528a26d54a925d1fef2c25585b4c8e4017 - languageName: node - linkType: hard - -"acorn@npm:^8.11.3": +"acorn@npm:^8.0.0, acorn@npm:^8.10.0, acorn@npm:^8.11.2, acorn@npm:^8.11.3, acorn@npm:^8.4.1, acorn@npm:^8.6.0, acorn@npm:^8.7.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": version: 8.11.3 resolution: "acorn@npm:8.11.3" bin: @@ -14252,33 +14222,36 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.18.0": - version: 0.18.20 - resolution: "esbuild@npm:0.18.20" - dependencies: - "@esbuild/android-arm": "npm:0.18.20" - "@esbuild/android-arm64": "npm:0.18.20" - "@esbuild/android-x64": "npm:0.18.20" - "@esbuild/darwin-arm64": "npm:0.18.20" - "@esbuild/darwin-x64": "npm:0.18.20" - "@esbuild/freebsd-arm64": "npm:0.18.20" - "@esbuild/freebsd-x64": "npm:0.18.20" - "@esbuild/linux-arm": "npm:0.18.20" - "@esbuild/linux-arm64": "npm:0.18.20" - "@esbuild/linux-ia32": "npm:0.18.20" - "@esbuild/linux-loong64": "npm:0.18.20" - "@esbuild/linux-mips64el": "npm:0.18.20" - "@esbuild/linux-ppc64": "npm:0.18.20" - "@esbuild/linux-riscv64": "npm:0.18.20" - "@esbuild/linux-s390x": "npm:0.18.20" - "@esbuild/linux-x64": "npm:0.18.20" - "@esbuild/netbsd-x64": "npm:0.18.20" - "@esbuild/openbsd-x64": "npm:0.18.20" - "@esbuild/sunos-x64": "npm:0.18.20" - "@esbuild/win32-arm64": "npm:0.18.20" - "@esbuild/win32-ia32": "npm:0.18.20" - "@esbuild/win32-x64": "npm:0.18.20" +"esbuild@npm:^0.20.1": + version: 0.20.1 + resolution: "esbuild@npm:0.20.1" + dependencies: + "@esbuild/aix-ppc64": "npm:0.20.1" + "@esbuild/android-arm": "npm:0.20.1" + "@esbuild/android-arm64": "npm:0.20.1" + "@esbuild/android-x64": "npm:0.20.1" + "@esbuild/darwin-arm64": "npm:0.20.1" + "@esbuild/darwin-x64": "npm:0.20.1" + "@esbuild/freebsd-arm64": "npm:0.20.1" + "@esbuild/freebsd-x64": "npm:0.20.1" + "@esbuild/linux-arm": "npm:0.20.1" + "@esbuild/linux-arm64": "npm:0.20.1" + "@esbuild/linux-ia32": "npm:0.20.1" + "@esbuild/linux-loong64": "npm:0.20.1" + "@esbuild/linux-mips64el": "npm:0.20.1" + "@esbuild/linux-ppc64": "npm:0.20.1" + "@esbuild/linux-riscv64": "npm:0.20.1" + "@esbuild/linux-s390x": "npm:0.20.1" + "@esbuild/linux-x64": "npm:0.20.1" + "@esbuild/netbsd-x64": "npm:0.20.1" + "@esbuild/openbsd-x64": "npm:0.20.1" + "@esbuild/sunos-x64": "npm:0.20.1" + "@esbuild/win32-arm64": "npm:0.20.1" + "@esbuild/win32-ia32": "npm:0.20.1" + "@esbuild/win32-x64": "npm:0.20.1" dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true "@esbuild/android-arm": optional: true "@esbuild/android-arm64": @@ -14325,7 +14298,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 473b1d92842f50a303cf948a11ebd5f69581cd254d599dd9d62f9989858e0533f64e83b723b5e1398a5b488c0f5fd088795b4235f65ecaf4f007d4b79f04bc88 + checksum: 7e0303cb80defd55f3f7b85108081afc9c2f3852dda13bf70975a89210f20cd658fc02540d34247401806cb069c4ec489f7cf0df833e040ee361826484926c3a languageName: node linkType: hard @@ -25022,20 +24995,7 @@ __metadata: languageName: node linkType: hard -"recast@npm:^0.23.1, recast@npm:^0.23.3": - version: 0.23.4 - resolution: "recast@npm:0.23.4" - dependencies: - assert: "npm:^2.0.0" - ast-types: "npm:^0.16.1" - esprima: "npm:~4.0.0" - source-map: "npm:~0.6.1" - tslib: "npm:^2.0.1" - checksum: d719633be8029e28f23b8191d4a525c5dbdac721792ab3cb5e9dfcf1694fb93f3c147b186916195a9c7fa0711f1e4990ba457cdcee02faed3899d4a80da1bd1f - languageName: node - linkType: hard - -"recast@npm:^0.23.5": +"recast@npm:^0.23.1, recast@npm:^0.23.3, recast@npm:^0.23.5": version: 0.23.5 resolution: "recast@npm:0.23.5" dependencies: @@ -28073,14 +28033,7 @@ __metadata: languageName: node linkType: hard -"tiny-invariant@npm:^1.3.1": - version: 1.3.1 - resolution: "tiny-invariant@npm:1.3.1" - checksum: 5b87c1d52847d9452b60d0dcb77011b459044e0361ca8253bfe7b43d6288106e12af926adb709a6fc28900e3864349b91dad9a4ac93c39aa15f360b26c2ff4db - languageName: node - linkType: hard - -"tiny-invariant@npm:^1.3.3": +"tiny-invariant@npm:^1.3.1, tiny-invariant@npm:^1.3.3": version: 1.3.3 resolution: "tiny-invariant@npm:1.3.3" checksum: 65af4a07324b591a059b35269cd696aba21bef2107f29b9f5894d83cc143159a204b299553435b03874ebb5b94d019afa8b8eff241c8a4cfee95872c2e1c1c4a diff --git a/docs/addons/addons-api.md b/docs/addons/addons-api.md index 8bdc7d62367e..c2c1afad9edb 100644 --- a/docs/addons/addons-api.md +++ b/docs/addons/addons-api.md @@ -244,32 +244,32 @@ This method allows you to override the default Storybook UI configuration (e.g., The following table details how to use the API values: -| Name | Type | Description | Example Value | -| --------------------- | :-------------: | :-----------------------------------------------------: | :-----------------------------------: | -| **navSize** | Number (pixels) | The size of the sidebar that shows a list of stories | `300` | -| **bottomPanelHeight** | Number (pixels) | The size of the addon panel when in the bottom position | `200` | -| **rightPanelWidth** | Number (pixels) | The size of the addon panel when in the right position | `200` | -| **panelPosition** | String | Where to show the addon panel | `'bottom'` or `'right'` | -| **enableShortcuts** | Boolean | Enable/disable shortcuts | `true` | -| **showToolbar** | Boolean | Show/hide toolbar | `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` | -| **sidebar** | Object | Sidebar options, see below | `{ showRoots: false }` | -| **toolbar** | Object | Modify the tools in the toolbar using the addon id | `{ fullscreen: { hidden: false } } }` | +| Name | Type | Description | Example Value | +| --------------------- | --------------- | ------------------------------------------------------- | ------------------------------------- | +| **navSize** | Number (pixels) | The size of the sidebar that shows a list of stories | `300` | +| **bottomPanelHeight** | Number (pixels) | The size of the addon panel when in the bottom position | `200` | +| **rightPanelWidth** | Number (pixels) | The size of the addon panel when in the right position | `200` | +| **panelPosition** | String | Where to show the addon panel | `'bottom'` or `'right'` | +| **enableShortcuts** | Boolean | Enable/disable shortcuts | `true` | +| **showToolbar** | Boolean | Show/hide toolbar | `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` | +| **sidebar** | Object | Sidebar options, see below | `{ showRoots: false }` | +| **toolbar** | Object | Modify the tools in the toolbar using the addon id | `{ fullscreen: { hidden: false } } }` | The following options are configurable under the `sidebar` namespace: -| Name | Type | Description | Example Value | -| ------------------ | :------: | :-----------------------------------------------------------: | :----------------------------------------------: | -| **showRoots** | Boolean | Display the top-level nodes as a "root" in the sidebar | `false` | -| **collapsedRoots** | Array | Set of root node IDs to visually collapse by default | `['misc', 'other']` | +| Name | Type | Description | Example Value | +| ------------------ | -------- | ------------------------------------------------------------- | ------------------------------------------------ | +| **showRoots** | Boolean | Display the top-level nodes as a "root" in the sidebar | `false` | +| **collapsedRoots** | Array | Set of root node IDs to visually collapse by default | `['misc', 'other']` | | **renderLabel** | Function | Create a custom label for tree nodes; must return a ReactNode | `(item) => {item.name}` | The following options are configurable under the `toolbar` namespace: -| Name | Type | Description | Example Value | -| ------ | :----: | :--------------------------------: | :-----------------: | +| Name | Type | Description | Example Value | +| ------ | ------ | ---------------------------------- | ------------------- | | **id** | String | Toggle visibility for toolbar item | `{ hidden: false }` | --- diff --git a/docs/api/csf.md b/docs/api/csf.md index b91f45fd57a4..636195be3761 100644 --- a/docs/api/csf.md +++ b/docs/api/csf.md @@ -64,11 +64,11 @@ With CSF, every named export in the file represents a story object by default. The exported identifiers will be converted to "start case" using Lodash's [startCase](https://lodash.com/docs/#startCase) function. For example: -| Identifier | Transformation | -| ---------------- | :---------------: | -| name | Name | -| someName | Some Name | -| someNAME | Some NAME | +| Identifier | Transformation | +| ---------------- | ----------------- | +| name | Name | +| someName | Some Name | +| someNAME | Some NAME | | some_custom_NAME | Some Custom NAME | | someName1234 | Some Name 1 2 3 4 | diff --git a/docs/configure/features-and-behavior.md b/docs/configure/features-and-behavior.md index a81aa6a55c74..ebfe081a0645 100644 --- a/docs/configure/features-and-behavior.md +++ b/docs/configure/features-and-behavior.md @@ -16,44 +16,44 @@ To control the layout of Storybook’s UI you can use `addons.setConfig` in your The following table details how to use the API values: -| Name | Type | Description | Example Value | -| --------------------- | :-------------: | :-----------------------------------------------------: | :-------------------------------------: | -| **navSize** | Number (pixels) | The size of the sidebar that shows a list of stories | `300` | -| **bottomPanelHeight** | Number (pixels) | The size of the addon panel when in the bottom position | `200` | -| **rightPanelWidth** | Number (pixels) | The size of the addon panel when in the right position | `200` | -| **panelPosition** | String | Where to show the addon panel | `'bottom'` or `'right'` | -| **enableShortcuts** | Boolean | Enable/disable shortcuts | `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'` | -| **sidebar** | Object | Sidebar options, see below | `{ showRoots: false }` | -| **toolbar** | Object | Modify the tools in the toolbar using the addon id | `{ fullscreen: { hidden: false } } }` | +| Name | Type | Description | Example Value | +| --------------------- | --------------- | ------------------------------------------------------- | --------------------------------------- | +| **navSize** | Number (pixels) | The size of the sidebar that shows a list of stories | `300` | +| **bottomPanelHeight** | Number (pixels) | The size of the addon panel when in the bottom position | `200` | +| **rightPanelWidth** | Number (pixels) | The size of the addon panel when in the right position | `200` | +| **panelPosition** | String | Where to show the addon panel | `'bottom'` or `'right'` | +| **enableShortcuts** | Boolean | Enable/disable shortcuts | `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'` | +| **sidebar** | Object | Sidebar options, see below | `{ showRoots: false }` | +| **toolbar** | Object | Modify the tools in the toolbar using the addon id | `{ fullscreen: { hidden: false } } }` | The following options are configurable under the `sidebar` namespace: -| Name | Type | Description | Example Value | -| ------------------ | :------: | :-----------------------------------------------------------: | :----------------------------------------------: | -| **showRoots** | Boolean | Display the top-level nodes as a "root" in the sidebar | `false` | -| **collapsedRoots** | Array | Set of root node IDs to visually collapse by default | `['misc', 'other']` | +| Name | Type | Description | Example Value | +| ------------------ | -------- | ------------------------------------------------------------- | ------------------------------------------------ | +| **showRoots** | Boolean | Display the top-level nodes as a "root" in the sidebar | `false` | +| **collapsedRoots** | Array | Set of root node IDs to visually collapse by default | `['misc', 'other']` | | **renderLabel** | Function | Create a custom label for tree nodes; must return a ReactNode | `(item) => {item.name}` | The following options are configurable under the `toolbar` namespace: -| Name | Type | Description | Example Value | -| ------ | :----: | :--------------------------------: | :-----------------: | +| Name | Type | Description | Example Value | +| ------ | ------ | ---------------------------------- | ------------------- | | **id** | String | Toggle visibility for toolbar item | `{ hidden: false }` | ## Configuring through URL parameters You can use URL parameters to configure some of the available features: -| Config option | Query param | Supported values | -| ------------------- | :----------: | :----------------------------: | -| **enableShortcuts** | `shortcuts` | `false` | -| --- (fullscreen) | `full` | `true`, `false` | -| --- (show sidebar) | `nav` | `true`, `false` | -| --- (show panel) | `panel` | `false`, `'right'`, `'bottom'` | -| **selectedPanel** | `addonPanel` | Any panel ID | -| **showTabs** | `tabs` | `true` | -| --- | `instrument` | `false`, `true` | +| Config option | Query param | Supported values | +| ------------------- | ------------ | ------------------------------ | +| **enableShortcuts** | `shortcuts` | `false` | +| --- (fullscreen) | `full` | `true`, `false` | +| --- (show sidebar) | `nav` | `true`, `false` | +| --- (show panel) | `panel` | `false`, `'right'`, `'bottom'` | +| **selectedPanel** | `addonPanel` | Any panel ID | +| **showTabs** | `tabs` | `true` | +| --- | `instrument` | `false`, `true` | diff --git a/docs/essentials/toolbars-and-globals.md b/docs/essentials/toolbars-and-globals.md index 125499681c61..9c33e952354b 100644 --- a/docs/essentials/toolbars-and-globals.md +++ b/docs/essentials/toolbars-and-globals.md @@ -144,13 +144,12 @@ By adding the configuration element `right`, the text will be displayed on the r Here's a list of the configuration options available. -| MenuItem | Type | Description | Required | -| --------- | :----: | :-------------------------------------------------------------: | :------: | -| **value** | String | The string value of the menu that gets set in the globals | Yes | -| **title** | String | The main text of the title | Yes | -| **left** | String | A string that gets shown on the left side of the menu | No | -| **right** | String | A string that gets displayed on the right side of the menu | No | -| **icon** | String | An icon that gets shown in the toolbar if this item is selected | No | +| MenuItem | Type | Description | Required | +| --------- | ------ | --------------------------------------------------------------- | -------- | +| **value** | String | The string value of the menu that gets set in the globals | Yes | +| **title** | String | The main text of the title | Yes | +| **right** | String | A string that gets displayed on the right side of the menu | No | +| **icon** | String | An icon that gets shown in the toolbar if this item is selected | No | ## Consuming globals from within a story diff --git a/docs/faq.md b/docs/faq.md index 1cceba208102..aa597d0f4f57 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,36 +2,7 @@ title: 'Frequently Asked Questions' --- -Here are some answers to frequently asked questions. If you have a question, you can ask it by opening an issue on the [Storybook Repository](https://github.com/storybookjs/storybook/). - -- [Error: No angular.json file found](#error-no-angularjson-file-found) -- [How can I opt-out of Angular Ivy?](#how-can-i-opt-out-of-angular-ivy) -- [How can I opt-out of Angular ngcc?](#how-can-i-opt-out-of-angular-ngcc) -- [How can I run coverage tests with Create React App and leave out stories?](#how-can-i-run-coverage-tests-with-create-react-app-and-leave-out-stories) -- [How do I setup Storybook to share Webpack configuration with Next.js?](#how-do-i-setup-storybook-to-share-webpack-configuration-with-nextjs) -- [How do I fix module resolution in special environments?](#how-do-i-fix-module-resolution-in-special-environments) -- [How do I setup the new React Context Root API with Storybook?](#how-do-i-setup-the-new-react-context-root-api-with-storybook) -- [Why is there no addons channel?](#why-is-there-no-addons-channel) -- [Why aren't Controls visible in the Canvas panel but visible in Docs?](#why-arent-controls-visible-in-the-canvas-panel-but-visible-in-docs) -- [Why aren't the addons working in a composed Storybook?](#why-arent-the-addons-working-in-a-composed-storybook) -- [Can I have a Storybook with no local stories?](#can-i-have-a-storybook-with-no-local-stories) -- [Which community addons are compatible with the latest version of Storybook?](#which-community-addons-are-compatible-with-the-latest-version-of-storybook) -- [Is it possible to browse the documentation for past versions of Storybook?](#is-it-possible-to-browse-the-documentation-for-past-versions-of-storybook) -- [What icons are available for my toolbar or my addon?](#what-icons-are-available-for-my-toolbar-or-my-addon) -- [I see a "No Preview" error with a Storybook production build](#i-see-a-no-preview-error-with-a-storybook-production-build) -- [Can I use Storybook with Vue 2?](#can-i-use-storybook-with-vue-2) -- [Why aren't my code blocks highlighted with Storybook MDX](#why-arent-my-code-blocks-highlighted-with-storybook-mdx) -- [Why aren't my MDX stories working in Storybook?](#why-arent-my-mdx-stories-working-in-storybook) -- [Why are my mocked GraphQL queries failing with Storybook's MSW addon?](#why-are-my-mocked-graphql-queries-failing-with-storybooks-msw-addon) -- [Can I use other GraphQL providers with Storybook's MSW addon?](#can-i-use-other-graphql-providers-with-storybooks-msw-addon) -- [Can I mock GraphQL mutations with Storybook's MSW addon?](#can-i-mock-graphql-mutations-with-storybooks-msw-addon) -- [How can my code detect if it is running in Storybook?](#how-can-my-code-detect-if-it-is-running-in-storybook) -- [Why are my stories not showing up correctly when using certain characters?](#why-are-my-stories-not-showing-up-correctly-when-using-certain-characters) -- [Why are the TypeScript examples and documentation using `as` for type safety?](#why-are-the-typescript-examples-and-documentation-using-as-for-type-safety) -- [Why is Storybook's source loader returning undefined with curried functions?](#why-is-storybooks-source-loader-returning-undefined-with-curried-functions) -- [Why are my args no longer displaying the default values?](#why-are-my-args-no-longer-displaying-the-default-values) -- [Why isn't Storybook's test runner working?](#why-isnt-storybooks-test-runner-working) -- [How does Storybook handle environment variables?](#how-does-storybook-handle-environment-variables) +Here are some answers to frequently asked questions. If you have a question, you can ask it in our [GitHub discussions](https://github.com/storybookjs/storybook/discussions/new?category=help). ## Error: No angular.json file found @@ -222,7 +193,7 @@ Starting with Storybook version 6.0, we've introduced some great features aimed With this, we would like to point out that if you plan on using addons created by our fantastic community, you need to consider that some of those addons might be working with an outdated version of Storybook. -We're actively working to provide a better way to address this situation, but in the meantime, we would ask for a bit of caution on your end so that you don't run into unexpected problems. Let us know by creating an issue in the [Storybook repo](https://github.com/storybookjs/storybook/issues) so that we can gather information and create a curated list with those addons to help not only you but the rest of the community. +We're actively working to provide a better way to address this situation, but in the meantime, we'd like to ask for a bit of caution on your end so that you don't run into unexpected problems. Let us know by leaving a comment in the following [GitHub issue](https://github.com/storybookjs/storybook/issues/26031) so that we can gather information and expand the current list of addons that need to be updated to work with the latest version of Storybook. ## Is it possible to browse the documentation for past versions of Storybook? @@ -368,9 +339,9 @@ Vue 2 entered [End of Life](https://v2.vuejs.org/lts/) (EOL) on December 31, 202 @@ -465,30 +436,6 @@ You can do this by checking for the `IS_STORYBOOK` global variable, which will e Storybook allows you to use most characters while naming your stories. Still, specific characters (e.g., `#`) can lead to issues when Storybook generates the internal identifier for the story, leading to collisions and incorrectly outputting the correct story. We recommend using such characters sparsely. -## Why are the TypeScript examples and documentation using `as` for type safety? - -We're aware that the default Typescript story construct might seem outdated and could potentially introduce a less than ideal way of handling type safety and strictness and could be rewritten as such: - -```ts -// Button.stories.ts|tsx - -import React from 'react'; -import type { ComponentStory, ComponentMeta } from '@storybook/react'; - -const StoryMeta: ComponentMeta = { - /* πŸ‘‡ The title prop is optional. - * See https://storybook.js.org/docs/configure/#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'Button', - component: Button, -}; - -export default meta; -``` - -Although valid, it introduces additional boilerplate code to the story definition. Instead, we're working towards implementing a safer mechanism based on what's currently being discussed in the following [issue](https://github.com/microsoft/TypeScript/issues/7481). Once the feature is released, we'll migrate our existing examples and documentation accordingly. - ## Why is Storybook's source loader returning undefined with curried functions? This is a known issue with Storybook. If you're interested in getting it fixed, open an issue with a [working reproduction](./contribute/how-to-reproduce.md) so that it can be triaged and fixed in future releases. diff --git a/docs/get-started/angular.md b/docs/get-started/angular.md new file mode 100644 index 000000000000..e5c706acc1df --- /dev/null +++ b/docs/get-started/angular.md @@ -0,0 +1,445 @@ +--- +title: Storybook for Angular +--- + +export const SUPPORTED_RENDERER = 'angular'; + +Storybook for Angular is a [framework](../contribute/framework.md) that makes it easy to develop and test UI components in isolation for [Angular](https://angular.io/) applications. It includes: + +- 🧱 Uses Angular builders +- πŸŽ›οΈ Compodoc integration +- πŸ’« and more! + + + + + +Storybook for Angular is only supported in [Angular](?renderer=angular) projects. + + + + + + + + + +## Requirements + +- Angular β‰₯ 15.0 < 18.0 +- Webpack β‰₯ 5.0 +- Storybook β‰₯ 8.0 + +## Getting started + +### In a project without Storybook + +Follow the prompts after running this command in your Angular project's root directory: + + + + + + + +[More on getting started with Storybook.](./install.md) + +### In a project with Storybook + +This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: + + + + + + + +#### Automatic migration + +When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/angular`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. + +#### Manual migration + +First, install the framework: + + + + + + + +Then, update your `.storybook/main.js|ts` to change the framework property: + + + + + + + +Finally, update your `angular.json` to include the Storybook builder: + +```jsonc +// angular.json +{ + ... + "projects": { + ... + "your-project": { + ... + "architect": { + ... + "storybook": { + "builder": "@storybook/angular:start-storybook", + "options": { + // The path to the storybook config directory + "configDir": ".storybook", + // The build target of your project + "browserTarget": "your-project:build", + // The port you want to start Storybook on + "port": 6006 + // More options available, documented here: + // https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular/src/builders/start-storybook/schema.json + } + }, + "build-storybook": { + "builder": "@storybook/angular:build-storybook", + "options": { + "configDir": ".storybook", + "browserTarget": "your-project:build", + "outputDir": "dist/storybook/your-project" + // More options available, documented here: + // https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular/src/builders/build-storybook/schema.json + } + } + } + } + } +} +``` + +## Run Storybook + +To run Storybook for a particular project, please run: + +```sh +ng run :storybook +``` + +To build Storybook, run: + +```sh +ng run :build-storybook +``` + +You will find the output in the configured `outputDir` (default is `dist/storybook/`). + +## Setup Compodoc + +You can include JSDoc comments above components, directives, and other parts of your Angular code to include documentation for those elements. Compodoc uses these comments to [generate documentation](../writing-docs/autodocs.md) for your application. In Storybook, it is useful to add explanatory comments above `@Inputs` and `@Outputs`, since these are the main elements that Storybook displays in its user interface. The `@Inputs` and `@Outputs` are the elements that you can interact with in Storybook, such as [controls](../essentials/controls.md). + +### Automatic setup + +When installing Storybook via `npx storybook@latest init`, you will be given the option to set up Compodoc automatically. + +### Manual setup + +If you have already installed Storybook, you can set up Compodoc manually. + +Install the following dependencies: + +```sh +npm install --save-dev @compodoc/compodoc +``` + +Add the following option to your Storybook Builder: + +```jsonc +// angular.json +{ + ... + "projects": { + ... + "your-project": { + ... + "architect": { + ... + "storybook": { + "builder": "@storybook/angular:start-storybook", + "options": { + ... + // πŸ‘‡ Add these + "compodoc": true, + "compodocArgs": [ + "-e", + "json", + "-d", + // Where to store the generated documentation. It's usually the root of your Angular project. It's not necessarily the root of your Angular Workspace! + "." + ], + } + }, + "build-storybook": { + "builder": "@storybook/angular:build-storybook", + "options": { + ... + // πŸ‘‡ Add these + "compodoc": true, + "compodocArgs": [ + "-e", + "json", + "-d", + "." + ], + } + } + } + } + } +} +``` + +Go to your `.storybook/preview.js` and add the following: + +```js +// .storybook/preview.js +// πŸ‘‡ Add these +import { setCompodocJson } from '@storybook/addon-docs/angular'; +import docJson from '../documentation.json'; +setCompodocJson(docJson); + +// ... rest of file +``` + +## `applicationConfig` decorator + +If your component relies on application-wide providers, like the ones defined by BrowserAnimationsModule or any other modules which use the forRoot pattern to provide a ModuleWithProviders, you can apply the `applicationConfig` [decorator](../writing-stories/decorators.md) to all stories for that component. This will provide them to the [bootstrapApplication function](https://angular.io/guide/standalone-components#configuring-dependency-injection), which is used to bootstrap the component in Storybook. + +```ts +// ChipsModule.stories.ts +import { Meta, applicationConfig, StoryObj } from '@storybook/angular'; +import { BrowserAnimationsModule, provideAnimations } from '@angular/platform-browser/animations'; +import { importProvidersFrom } from '@angular/core'; + +import { ChipsModule } from './angular-src/chips.module'; + +const meta: Meta = { + component: ChipsModule, + decorators: [ + // Apply application config to all stories + applicationConfig({ + // List of providers and environment providers that should be available to the root component and all its children. + providers: [ + ... + // Import application-wide providers from a module + importProvidersFrom(BrowserAnimationsModule) + // Or use provide-style functions if available instead, e.g. + provideAnimations() + ], + }), + ], +}; + +export default meta; + +type Story = StoryObj; + +export const WithCustomApplicationProvider: Story = { + render: () => ({ + // Apply application config to a specific story + applicationConfig: { + // The providers will be merged with the ones defined in the applicationConfig decorators providers array of the global meta object + providers: [...], + } + }) +} +``` + +## `moduleMetadata` decorator + +If your component has dependencies on other Angular directives and modules, these can be supplied using the `moduleMetadata` [decorator](../writing-stories/decorators.md) either for all stories of a component or for individual stories. + +```ts +// YourComponent.stories.ts +import { Meta, moduleMetadata, StoryObj } from '@storybook/angular'; + +import { YourComponent } from './your.component'; + +const meta: Meta = { + component: YourComponent, + decorators: [ + // Apply metadata to all stories + moduleMetadata({ + // import necessary ngModules or standalone components + imports: [...], + // declare components that are used in the template + declarations: [...], + // List of providers that should be available to the root component and all its children. + providers: [...], + }), + ], +}; +export default meta; + +type Story = StoryObj; + +export const Base: Story = {}; + +export const WithCustomProvider: Story = { + decorators: [ + // Apply metadata to a specific story + moduleMetadata({ + imports: [...], + declarations: [...], + providers: [...], + }), + ], +}; +``` + +## FAQ + +### How do I migrate to an Angular Storybook builder? + +The Storybook [Angular builder](https://angular.io/guide/glossary#builder) is a way to run Storybook in an Angular workspace. It is a drop-in replacement for running `storybook dev` and `storybook build` directly. + +You can run `npx storybook@next automigrate` to try let Storybook detect and automatically fix your configuration. Otherwise, you can follow the next steps to manually adjust your configuration. + +#### Do you have only one Angular project in your workspace? + +First, go to your `angular.json` and add `storybook` and `build-storybook` entries in `architect` section of your project like shown above. + +Second, adjust your `package.json` script section. Usually, it will look like this: + +```jsonc +{ + "scripts": { + "storybook": "start-storybook -p 6006", // or `storybook dev -p 6006` + "build-storybook": "build-storybook" // or `storybook build` + } +} +``` + +Now, you can run Storybook with `ng run :storybook` and build it with `ng run :build-storybook`. Adjust the scripts in your `package.json` accordingly. + +```json +{ + "scripts": { + "storybook": "ng run :storybook", + "build-storybook": "ng run :build-storybook" + } +} +``` + +Also compodoc is now built-in in `@storybook/angular` and you don't have to call it explicitly. If were running compodoc in your `package.json` scripts like this: + +```json +{ + "scripts": { + "docs:json": "compodoc -p tsconfig.json -e json -d ./documentation", + "storybook": "npm run docs:json && start-storybook -p 6006", + "build-storybook": "npm run docs:json && build-storybook" + } +} +``` + +Change it to: + +```json +{ + "scripts": { + "storybook": "ng run :storybook", + "build-storybook": "ng run :build-storybook" + } +} +``` + +#### I have multiple projects in my Angular workspace + +In this case you have to adjust your `angular.json` and `package.json` as described above for each project in which you want to use Storybook. Please note, that each project should have a dedicated `.storybook` folder, which should be placed in the root of the project. + +You can run `npx storybook@latest init` sequentially for each project to setup Storybook for each of them to automatically create the `.storybook` folder and create the necessary configuration in your `angular.json`. + +You can then use [Storybook composition](https://storybook.js.org/docs/angular/sharing/storybook-composition) to composite multiple Storybooks into one. + +### How do I configure Angular's builder for Storybook? + +These are common options you may need for the Angular builder: + +| Configuration element | Description | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `"browserTarget"` | Build target to be served using the following format.
`"example-project:builder:config"` | +| `"tsConfig"` | Location of the TypeScript configuration file, relative to the current workspace.
`"tsConfig": "./tsconfig.json"`. | +| `"port"` | Port used by Storybook.
`"port": 6006` | +| `"host"` | Set up a custom host for Storybook.
`"host": "http://my-custom-host"` | +| `"configDir"` | Storybook configuration directory location.
`"configDir": ".storybook"` | +| `"https"` | Starts Storybook with HTTPS enabled.
`"https": true`
Requires custom certificate information. | +| `"sslCa"` | Provides an SSL certificate authority.
`"sslCa": "your-custom-certificate-authority"`
Optional usage with `"https"` | +| `"sslCert"` | Provides an SSL certificate.
`"sslCert": "your-custom-certificate"`
Required for `https` | +| `"sslKey"` | Provides an SSL key to serve Storybook.
`"sslKey": "your-ssl-key"` | +| `"smokeTest"` | Exit Storybook after successful start.
`"smokeTest": true` | +| `"ci"` | Starts Storybook in CI mode (skips interactive prompts and will not open browser window).
`"ci": true` | +| `"quiet"` | Filters Storybook verbose build output.
`"quiet": true` | +| `"docs"` | Starts Storybook in [documentation mode](../writing-docs/build-documentation.md#preview-storybooks-documentation).
`"docs": true` | +| `"styles"` | Provide the location of the [application's styles](../configure/styling-and-css.md#importing-css-files) to be used with Storybook.
`"styles": ["src/styles.css", "src/styles.scss"]`
| +| `"stylePreprocessorOptions"` | Provides further customization for style preprocessors resolved to the workspace root.
`"stylePreprocessorOptions": { "includePaths": ["src/styles"] }` | + +The full list of options can be found in the Angular builder schemas: + +- [Build Storybook](https://github.com/storybookjs/storybook/blob/main/code/frameworks/angular/src/builders/build-storybook/schema.json) +- [Start Storybook](https://github.com/storybookjs/storybook/blob/main/code/frameworks/angular/src/builders/start-storybook/schema.json) + +## API + +### Options + +You can pass an options object for additional configuration if needed: + +```js +// .storybook/main.js +import * as path from 'path'; + +export default { + // ... + framework: { + name: '@storybook/angular', + options: { + // ... + }, + }, +}; +``` + +The available options are: + +#### `builder` + +Type: `Record` + +Configure options for the [framework's builder](../api/main-config-framework.md#optionsbuilder). For Angular, , available options can be found in the [Webpack builder docs](../builders/webpack.md). + + + +
diff --git a/docs/get-started/install.md b/docs/get-started/install.md index 70922d5c96e3..138c0164312b 100644 --- a/docs/get-started/install.md +++ b/docs/get-started/install.md @@ -203,9 +203,9 @@ Vue 2 entered [End of Life](https://v2.vuejs.org/lts/) (EOL) on December 31st, 2 diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md index 9c765276058b..6cbd4ac91177 100644 --- a/docs/get-started/nextjs.md +++ b/docs/get-started/nextjs.md @@ -2,6 +2,8 @@ title: Storybook for Next.js --- +export const SUPPORTED_RENDERER = 'react'; + Storybook for Next.js is a [framework](../contribute/framework.md) that makes it easy to develop and test UI components in isolation for [Next.js](https://nextjs.org/) applications. It includes: - πŸ”€ Routing @@ -11,10 +13,24 @@ Storybook for Next.js is a [framework](../contribute/framework.md) that makes it - πŸŽ› Webpack & Babel config - πŸ’« and more! + + + + +Storybook for Next.js is only supported in [React](?renderer=react) projects. + + + + + + + + + ## Requirements -- Next.js >= 13.5 -- Storybook >= 7.x +- Next.js β‰₯ 13.5 +- Storybook β‰₯ 7.0 ## Getting started @@ -38,7 +54,7 @@ Follow the prompts after running this command in your Next.js project's root dir ### In a project with Storybook -This framework is designed to work with Storybook 7. If you’re not already using v7, upgrade with this command: +This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: @@ -98,6 +114,14 @@ Finally, if you were using Storybook plugins to integrate with Next.js, those ar +## Run the Setup Wizard + +If all goes well, you should see a setup wizard that will help you get started with Storybook introducing you to the main concepts and features, including how the UI is organized, how to write your first story, and how to test your components' response to various inputs utilizing [controls](../essentials/controls). + +![Storybook onboarding](./example-onboarding-wizard.png) + +If you skipped the wizard, you can always run it again by adding the `?path=/onboarding` query parameter to the URL of your Storybook instance, provided that the example stories are still available. + ## Next.js's Image component This framework allows you to use Next.js's [next/image](https://nextjs.org/docs/pages/api-reference/components/image) with no configuration. @@ -920,7 +944,7 @@ The available options are: Type: `Record` -Configure options for the [framework's builder](../api/main-config-framework.md#optionsbuilder). For Next.js, that builder is Webpack 5. +Configure options for the [framework's builder](../api/main-config-framework.md#optionsbuilder). For Next.js, available options can be found in the [Webpack builder docs](../builders/webpack.md). #### `image` @@ -933,3 +957,7 @@ Props to pass to every instance of `next/image`. See [next/image docs](https://n Type: `string` The absolute path to the `next.config.js` file. This is necessary if you have a custom `next.config.js` file that is not in the root directory of your project. + + + + diff --git a/docs/get-started/react-vite.md b/docs/get-started/react-vite.md new file mode 100644 index 000000000000..9fbe5eca53af --- /dev/null +++ b/docs/get-started/react-vite.md @@ -0,0 +1,140 @@ +--- +title: Storybook for React & Vite +--- + +export const SUPPORTED_RENDERER = 'react'; + +Storybook for React & Vite is a [framework](../contribute/framework.md) that makes it easy to develop and test UI components in isolation for [React](https://react.dev/) applications built with [Vite](https://vitejs.dev/). It includes: + +- 🏎️ Pre-bundled for performance +- πŸͺ„ Zero config +- πŸ’« and more! + + + + + +Storybook for React & Vite is only supported in [React](?renderer=react) projects. + + + + + + + + + +## Requirements + +- React β‰₯ 16.8 +- Vite β‰₯ 4.0 +- Storybook β‰₯ 8.0 + +## Getting started + +### In a project without Storybook + +Follow the prompts after running this command in your React project's root directory: + + + + + + + +[More on getting started with Storybook.](./install.md) + +### In a project with Storybook + +This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: + + + + + + + +#### Automatic migration + +When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/react-vite`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. + +#### Manual migration + +First, install the framework: + + + + + + + +Then, update your `.storybook/main.js|ts` to change the framework property: + + + + + + + +## Run the Setup Wizard + +If all goes well, you should see a setup wizard that will help you get started with Storybook introducing you to the main concepts and features, including how the UI is organized, how to write your first story, and how to test your components' response to various inputs utilizing [controls](../essentials/controls). + +![Storybook onboarding](./example-onboarding-wizard.png) + +If you skipped the wizard, you can always run it again by adding the `?path=/onboarding` query parameter to the URL of your Storybook instance, provided that the example stories are still available. + +## API + +### Options + +You can pass an options object for additional configuration if needed: + +```ts +// .storybook/main.ts +import type { StorybookConfig } from '@storybook/react-vite'; + +const config: StorybookConfig = { + framework: { + name: '@storybook/react-vite', + options: { + // ... + }, + }, +}; + +export default config; +``` + +#### `builder` + +Type: `Record` + +Configure options for the [framework's builder](../api/main-config-framework.md#optionsbuilder). For this framework, available options can be found in the [Vite builder docs](../builders/vite.md). + + + + diff --git a/docs/get-started/react-webpack5.md b/docs/get-started/react-webpack5.md new file mode 100644 index 000000000000..769aa82a0f2d --- /dev/null +++ b/docs/get-started/react-webpack5.md @@ -0,0 +1,180 @@ +--- +title: Storybook for React & Webpack +--- + +export const SUPPORTED_RENDERER = 'react'; + +Storybook for React & Webpack is a [framework](../contribute/framework.md) that makes it easy to develop and test UI components in isolation for [React](https://react.dev/) applications built with [Webpack](https://webpack.js.org/). + + + + + +Storybook for React & Webpack is only supported in [React](?renderer=react) projects. + + + + + + + + + +## Requirements + +- React β‰₯ 16.8 +- Webpack β‰₯ 5.0 +- Storybook β‰₯ 8.0 + +## Getting started + +### In a project without Storybook + +Follow the prompts after running this command in your React project's root directory: + + + + + + + +[More on getting started with Storybook.](./install.md) + +### In a project with Storybook + +This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: + + + + + + + +#### Automatic migration + +When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/react-webpack5`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. + +#### Manual migration + +First, install the framework: + + + + + + + +Next, install and register your appropriate compiler addon, depending on whether you're using SWC (recommended) or Babel: + + + +If your project is using [Create React App](#create-react-app-cra), you can skip this step. + + + + + + + + + +or + + + + + + + +More details can be found in the [Webpack builder docs](../builders/webpack.md#compiler-support). + +Finally, update your `.storybook/main.js|ts` to change the framework property: + + + + + + + +## Run the Setup Wizard + +If all goes well, you should see a setup wizard that will help you get started with Storybook introducing you to the main concepts and features, including how the UI is organized, how to write your first story, and how to test your components' response to various inputs utilizing [controls](../essentials/controls). + +![Storybook onboarding](./example-onboarding-wizard.png) + +If you skipped the wizard, you can always run it again by adding the `?path=/onboarding` query parameter to the URL of your Storybook instance, provided that the example stories are still available. + +## Create React App (CRA) + +Support for [Create React App](https://create-react-app.dev/) is handled by [`@storybook/preset-create-react-app`](https://github.com/storybookjs/presets/tree/master/packages/preset-create-react-app). + +This preset enables support for all CRA features, including Sass/SCSS and TypeScript. + +If you're working on an app that was initialized manually (i.e., without the use of CRA), ensure that your app has [react-dom](https://www.npmjs.com/package/react-dom) included as a dependency. Failing to do so can lead to unforeseen issues with Storybook and your project. + +## API + +### Options + +You can pass an options object for additional configuration if needed: + +```ts +// .storybook/main.ts +import type { StorybookConfig } from '@storybook/react-webpack5'; + +const config: StorybookConfig = { + framework: { + name: '@storybook/react-webpack5', + options: { + // ... + }, + }, +}; + +export default config; +``` + +#### `builder` + +Type: `Record` + +Configure options for the [framework's builder](../api/main-config-framework.md#optionsbuilder). For this framework, available options can be found in the [Webpack builder docs](../builders/webpack.md). + + + + diff --git a/docs/get-started/svelte-vite.md b/docs/get-started/svelte-vite.md new file mode 100644 index 000000000000..2069a18d66ee --- /dev/null +++ b/docs/get-started/svelte-vite.md @@ -0,0 +1,153 @@ +--- +title: Storybook for Svelte & Vite +--- + +export const SUPPORTED_RENDERER = 'svelte'; + +Storybook for Svelte & Vite is a [framework](../contribute/framework.md) that makes it easy to develop and test UI components in isolation for applications using [Svelte](https://svelte.dev/) built with [Vite](https://vitejs.dev/). + + + + + +Storybook for Svelte & Vite is only supported in [Svelte](?renderer=svelte) projects. + + + + + + + + + +## Requirements + +- Svelte β‰₯ 4.0 +- Vite β‰₯ 4.0 +- Storybook β‰₯ 8.0 + +## Getting started + +### In a project without Storybook + +Follow the prompts after running this command in your Sveltekit project's root directory: + + + + + + + +[More on getting started with Storybook.](./install.md) + +### In a project with Storybook + +This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: + + + + + + + +#### Automatic migration + +When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/sveltekit`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. + +#### Manual migration + +First, install the framework: + + + + + + + +Then, update your `.storybook/main.js|ts` to change the framework property: + + + + + + + +## Writing native Svelte stories + +Storybook provides a Svelte addon maintained by the community, enabling you to write stories for your Svelte components using the template syntax. You'll need to take some additional steps to enable this feature. + +Run the following command to install the addon. + + + + + + + + + +The community actively maintains the Svelte CSF addon but still lacks some features currently available in the official Storybook Svelte framework support. For more information, see [addon's documentation](https://github.com/storybookjs/addon-svelte-csf). + + + +## API + +### Options + +You can pass an options object for additional configuration if needed: + +```js +// .storybook/main.js +import * as path from 'path'; + +export default { + // ... + framework: { + name: '@storybook/svelte-vite', + options: { + // ... + }, + }, +}; +``` + +The available options are: + +#### `builder` + +Type: `Record` + +Configure options for the [framework's builder](../api/main-config-framework.md#optionsbuilder). For this framework, available options can be found in the [Vite builder docs](../builders/vite.md). + + + + diff --git a/docs/get-started/vue-component-meta-event-types-controls.png b/docs/get-started/vue-component-meta-event-types-controls.png new file mode 100644 index 000000000000..093b2da0911a Binary files /dev/null and b/docs/get-started/vue-component-meta-event-types-controls.png differ diff --git a/docs/get-started/vue-component-meta-exposed-types-controls.png b/docs/get-started/vue-component-meta-exposed-types-controls.png new file mode 100644 index 000000000000..d725615e58b6 Binary files /dev/null and b/docs/get-started/vue-component-meta-exposed-types-controls.png differ diff --git a/docs/get-started/vue-component-meta-prop-types-controls.png b/docs/get-started/vue-component-meta-prop-types-controls.png new file mode 100644 index 000000000000..049bc595c2e9 Binary files /dev/null and b/docs/get-started/vue-component-meta-prop-types-controls.png differ diff --git a/docs/get-started/vue-component-meta-slot-types-controls.png b/docs/get-started/vue-component-meta-slot-types-controls.png new file mode 100644 index 000000000000..72b41db2dff8 Binary files /dev/null and b/docs/get-started/vue-component-meta-slot-types-controls.png differ diff --git a/docs/get-started/vue3-vite.md b/docs/get-started/vue3-vite.md new file mode 100644 index 000000000000..e79cc1f3b6fc --- /dev/null +++ b/docs/get-started/vue3-vite.md @@ -0,0 +1,346 @@ +--- +title: Storybook for Vue & Vite +--- + +export const SUPPORTED_RENDERER = 'vue'; + +Storybook for Vue & Vite is a [framework](../contribute/framework.md) that makes it easy to develop and test UI components in isolation for [Vue](https://vuejs.org/) applications built with [Vite](https://vitejs.dev/). It includes: + +- 🏎️ Pre-bundled for performance +- πŸͺ„ Zero config +- 🧠 Comprehensive docgen +- πŸ’« and more! + + + + + +Storybook for Vue & Vite is only supported in [Vue](?renderer=vue) projects. + + + + + + + + + +## Requirements + +- Vue β‰₯ 3 +- Vite β‰₯ 3.0 (4.X recommended) +- Storybook β‰₯ 7.0 + +## Getting started + +### In a project without Storybook + +Follow the prompts after running this command in your Vue project's root directory: + + + + + + + +[More on getting started with Storybook.](./install.md) + +### In a project with Storybook + +This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: + + + + + + + +#### Automatic migration + +When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/vue3-vite`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. + +#### Manual migration + +First, install the framework: + + + + + + + +Then, update your `.storybook/main.js|ts` to change the framework property: + + + + + + + +## Extending the Vue application + +Storybook creates a [Vue 3 application](https://vuejs.org/api/application.html#application-api) for your component preview. +When using global custom components (`app.component`), directives (`app.directive`), extensions (`app.use`), or other application methods, you will need to configure those in the `./storybook/preview.js` file. + +Therefore, Storybook provides you with a `setup` function exported from this package, which receives as a callback your Storybook instance, which you can interact with and add your custom configuration. + +```js +// .storybook/preview.js +import { setup } from '@storybook/vue3'; + +setup((app) => { + app.use(MyPlugin); + app.component('my-component', MyComponent); + app.mixin({ + /* My mixin */ + }); +}); + +// Rest of the file... +``` + +## Using `vue-component-meta` + + + +`vue-component-meta` is only available in Storybook β‰₯ 8. It is currently opt-in, but will become the default in a future version of Storybook. + + + +[`vue-component-meta`](https://github.com/vuejs/language-tools/tree/master/packages/component-meta) is a tool maintained by the Vue team that extracts metadata from Vue components. Storybook can use it to generate the [controls](../essentials/controls.md) for your stories and documentation. It's a more full-featured alternative to `vue-docgen-api` and is recommended for most projects. + +If you want to use `vue-component-meta`, you can configure it in your `.storybook/main.js|ts` file: + +```ts +// .storybook/main.ts +import type { StorybookConfig } from '@storybook/vue3-vite'; + +const config: StorybookConfig = { + framework: { + name: '@storybook/vue3-vite', + options: { + docgen: 'vue-component-meta', + }, + }, +}; + +export default config; +``` + +`vue-component-meta` comes with many benefits and enables more documentation features, such as: + +### Support for multiple component types + +`vue-component-meta` supports all types of Vue components (including SFC, functional, composition / options API components) from `.vue`, `.ts`, `.tsx`, `.js`, and `.jsx` files. + +It also supports both default and named component exports. + +### Prop description and JSDoc tag annotations + +To provide a description for a prop, including tags, you can use JSDoc comments in your component's props definition: + +```html + + +``` + +The props definition above will generate the following controls: + +![Controls generated from props](./vue-component-meta-prop-types-controls.png) + +### Events types extraction + +To provide a type for an emitted event, you can use TypeScript types (including JSDoc comments) in your component's `defineEmits` call: + +```html + + +``` + +Which will generate the following controls: + +![Controls generated from events](./vue-component-meta-event-types-controls.png) + +### Slots types extraction + +The slot types are automatically extracted from your component definition and displayed in the controls panel. + +```html + + + + +``` + +If you use `defineSlots`, you can provide a description for each slot using JSDoc comments in your component's slots definition: + +```ts +defineSlots<{ + /** Example description for default */ + default(props: { num: number }): any; + /** Example description for named */ + named(props: { str: string }): any; + /** Example description for no-bind */ + noBind(props: {}): any; + /** Example description for vbind */ + vbind(props: { num: number; str: string }): any; +}>(); +``` + +The definition above will generate the following controls: + +![Controls generated from slots](./vue-component-meta-slot-types-controls.png) + +### Exposed properties and methods + +The properties and methods exposed by your component are automatically extracted and displayed in the controls panel. + +```html + + +``` + +The definition above will generate the following controls: + +![Controls generated from exposed properties and methods](./vue-component-meta-exposed-types-controls.png) + +### Limitations + +`vue-component-meta` cannot currently reference types from an import alias. You will need to replace any aliased imports with relative ones, as in the example below. See [this issue](https://github.com/vuejs/language-tools/issues/3896) for more information. + +```ts +// YourComponent.ts +import type { MyProps } from '@/types'; // ❌ Cannot be resolved +import type { MyProps } from '../types'; // βœ… Can be resolved +``` + +## Troubleshooting + +### Storybook doesn't work with my Vue 2 project + +[Vue 2 entered End of Life](https://v2.vuejs.org/lts/) (EOL) on December 31st, 2023, and is no longer maintained by the Vue team. As a result, Storybook no longer supports Vue 2. We recommend you upgrade your project to Vue 3, which Storybook fully supports. If that's not an option, you can still use Storybook with Vue 2 by installing the latest version of Storybook 7 with the following command: + + + + + + + +## API + +### Options + +You can pass an options object for additional configuration if needed: + +```ts +// .storybook/main.ts +import type { StorybookConfig } from '@storybook/vue3-vite'; + +const config: StorybookConfig = { + framework: { + name: '@storybook/vue3-vite', + options: { + docgen: 'vue-component-meta', + }, + }, +}; + +export default config; +``` + +#### `builder` + +Type: `Record` + +Configure options for the [framework's builder](../api/main-config-framework.md#optionsbuilder). For this framework, available options can be found in the [Vite builder docs](../builders/vite.md). + +#### `docgen` + +Type: `'vue-docgen-api' | 'vue-component-meta'` + +Default: `'vue-docgen-api'` + +Since: `8.0` + +Choose which docgen tool to use when generating controls for your components. See [Using `vue-component-meta`](#using-vue-component-meta) for more information. + + + + diff --git a/docs/migration-guide/from-older-version.md b/docs/migration-guide/from-older-version.md index b3740aa2f854..c368c48e39be 100644 --- a/docs/migration-guide/from-older-version.md +++ b/docs/migration-guide/from-older-version.md @@ -2,11 +2,16 @@ title: 'Migration guide from Storybook 6.x to 8.0' --- -Storybook 8 focuses on performance and stability. - -- πŸ’¨ [2-4x faster test builds](/blog/optimize-storybook-7-6/#2-4x-faster-builds-with-thetest-flag), [25-50% faster React docgen](/blog/optimize-storybook-7-6/#22x-faster-react-docgen), and [SWC support for Webpack projects](/blog/optimize-storybook-7-6/#using-webpack-enable-swc) -- ✨ Improved framework support: you no longer need to install React as a peer dependency when using a non-React renderer -- 🌐 [Support for React Server Components (RSC)](/blog/storybook-react-server-components/): our experimental solution renders async RSC in the browser and mocks Node code +Storybook 8 focuses on improving performance, compatibility, and stability. Key features include: + +- 🩻 A new visual testing workflow via [the Visual Tests addon](https://www.chromatic.com/docs/visual-tests-addon/) +- πŸ’¨ [2-4x faster test builds](https://storybook.js.org/blog/optimize-storybook-7-6/#2-4x-faster-builds-with-thetest-flag), [25-50% faster React docgen](https://storybook.js.org/blog/optimize-storybook-7-6/#22x-faster-react-docgen), and [SWC support for Webpack projects](https://storybook.js.org/blog/optimize-storybook-7-6/#using-webpack-enable-swc) +- 🧩 Improved framework support: you no longer need to install React as a peer dependency when using a non-React renderer +- πŸŽ›οΈΒ Strengthened control generation in [React](https://storybook.js.org/blog/storybook-8-beta/#major-performance-improvements +) and [Vue](https://storybook.js.org/blog/first-class-vue-support-storybook-8/) projects +- ⚑️ Improved Vite architecture, Vitest testing, and Vite 5 support +- 🌐 [Support for React Server Components (RSC)](https://storybook.js.org/blog/storybook-react-server-components/): our experimental solution renders async RSC in the browser and mocks Node code +- ✨ A refreshed desktop UI & mobile UX - βž• Much, much more This guide is meant to help you **upgrade from Storybook 6.x to 8.0** successfully! @@ -79,6 +84,10 @@ If you are using the `storiesOf` API (which requires `storyStoreV7: false` in St Storybook 8 uses MDX 3. If you're coming from MDX 1 (used by Storybook 6), there were significant breaking changes in MDX 2. Please reference our [guidance on upgrading successfully](../../release-7-6/docs/migration-guide.md#upgrade-mdx1-to-mdx2). +#### Missing `vite.config.js` file + +If you are using Vite, you may now need to create a `vite.config.js` file in your project root to allow newer versions of Vite to work with Storybook. Additionally, you may need to install and configure a Vite plugin for your framework. More information is available in the [full migration notes](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#framework-specific-vite-plugins-have-to-be-explicitly-added). + ## Troubleshooting The automatic upgrade should get your Storybook into a working state. If you encounter an error running Storybook after upgrading, here’s what to do: diff --git a/docs/migration-guide/index.md b/docs/migration-guide/index.md index 70510a941d86..51962ca7b88d 100644 --- a/docs/migration-guide/index.md +++ b/docs/migration-guide/index.md @@ -2,11 +2,16 @@ title: 'Migration guide for Storybook 8.0' --- -Storybook 8 focuses on performance and stability. - -- πŸ’¨ [2-4x faster test builds](/blog/optimize-storybook-7-6/#2-4x-faster-builds-with-thetest-flag), [25-50% faster React docgen](/blog/optimize-storybook-7-6/#22x-faster-react-docgen), and [SWC support for Webpack projects](/blog/optimize-storybook-7-6/#using-webpack-enable-swc) -- ✨ Improved framework support: you no longer need to install React as a peer dependency when using a non-React renderer -- 🌐 [Support for React Server Components (RSC)](/blog/storybook-react-server-components/): our experimental solution renders async RSC in the browser and mocks Node code +Storybook 8 focuses on improving performance, compatibility, and stability. Key features include: + +- 🩻 A new visual testing workflow via [the Visual Tests addon](https://www.chromatic.com/docs/visual-tests-addon/) +- πŸ’¨ [2-4x faster test builds](https://storybook.js.org/blog/optimize-storybook-7-6/#2-4x-faster-builds-with-thetest-flag), [25-50% faster React docgen](https://storybook.js.org/blog/optimize-storybook-7-6/#22x-faster-react-docgen), and [SWC support for Webpack projects](https://storybook.js.org/blog/optimize-storybook-7-6/#using-webpack-enable-swc) +- 🧩 Improved framework support: you no longer need to install React as a peer dependency when using a non-React renderer +- πŸŽ›οΈΒ Strengthened control generation in [React](https://storybook.js.org/blog/storybook-8-beta/#major-performance-improvements +) and [Vue](https://storybook.js.org/blog/first-class-vue-support-storybook-8/) projects +- ⚑️ Improved Vite architecture, Vitest testing, and Vite 5 support +- 🌐 [Support for React Server Components (RSC)](https://storybook.js.org/blog/storybook-react-server-components/): our experimental solution renders async RSC in the browser and mocks Node code +- ✨ A refreshed desktop UI & mobile UX - βž• Much, much more This guide is meant to help you **upgrade from Storybook 7.x to 8.0** successfully! @@ -79,6 +84,10 @@ If you have `storyStoreV7: false` in your `.storybook/main.js`, you will need to If you are using the `storiesOf` API (which requires `storyStoreV7: false` in Storybook 7), you will need to either [migrate your stories to CSF](../../release-7-6/docs/migration-guide.md#storiesof-to-csf) or use the [new indexer API to continue creating stories dynamically](../../release-7-6/docs/migration-guide.md#storiesof-to-dynamically-created-stories). +#### Missing `vite.config.js` file + +If you are using Vite, you may now need to create a `vite.config.js` file in your project root to allow newer versions of Vite to work with Storybook. Additionally, you may need to install and configure a Vite plugin for your framework. More information is available in the [full migration notes](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#framework-specific-vite-plugins-have-to-be-explicitly-added). + ## New projects To add Storybook to a project that isn’t currently using Storybook: diff --git a/docs/snippets/angular/angular-add-framework.js.mdx b/docs/snippets/angular/angular-add-framework.js.mdx new file mode 100644 index 000000000000..00e5c2474ab6 --- /dev/null +++ b/docs/snippets/angular/angular-add-framework.js.mdx @@ -0,0 +1,7 @@ +```js +// .storybook/main.js +export default { + // ... + framework: '@storybook/angular', // πŸ‘ˆ Add this +}; +``` diff --git a/docs/snippets/angular/angular-add-framework.ts.mdx b/docs/snippets/angular/angular-add-framework.ts.mdx new file mode 100644 index 000000000000..cdc3c6ccd1e3 --- /dev/null +++ b/docs/snippets/angular/angular-add-framework.ts.mdx @@ -0,0 +1,11 @@ +```ts +// .storybook/main.ts +import { StorybookConfig } from '@storybook/angular'; + +const config: StorybookConfig = { + // ... + framework: '@storybook/angular', // πŸ‘ˆ Add this +}; + +export default config; +``` diff --git a/docs/snippets/angular/angular-install.npm.js.mdx b/docs/snippets/angular/angular-install.npm.js.mdx new file mode 100644 index 000000000000..c8728d58c565 --- /dev/null +++ b/docs/snippets/angular/angular-install.npm.js.mdx @@ -0,0 +1,3 @@ +```shell +npm install --save-dev @storybook/angular +``` diff --git a/docs/snippets/angular/angular-install.pnpm.js.mdx b/docs/snippets/angular/angular-install.pnpm.js.mdx new file mode 100644 index 000000000000..5467721722b2 --- /dev/null +++ b/docs/snippets/angular/angular-install.pnpm.js.mdx @@ -0,0 +1,3 @@ +```shell +pnpm install --save-dev @storybook/angular +``` diff --git a/docs/snippets/angular/angular-install.yarn.js.mdx b/docs/snippets/angular/angular-install.yarn.js.mdx new file mode 100644 index 000000000000..9943e0163db2 --- /dev/null +++ b/docs/snippets/angular/angular-install.yarn.js.mdx @@ -0,0 +1,3 @@ +```shell +yarn add --dev @storybook/angular +``` diff --git a/docs/snippets/common/individual-snapshot-tests-portable-stories.jest.js.mdx b/docs/snippets/common/individual-snapshot-tests-portable-stories.jest.js.mdx new file mode 100644 index 000000000000..e9523fa3b322 --- /dev/null +++ b/docs/snippets/common/individual-snapshot-tests-portable-stories.jest.js.mdx @@ -0,0 +1,69 @@ +```js +// storybook.test.js +import path from 'path'; +import * as glob from 'glob'; + +//πŸ‘‡ Augment expect with jest-specific-snapshot +import 'jest-specific-snapshot'; + +import { describe, test, expect } from '@jest/globals'; + +// Replace your-testing-library with one of the supported testing libraries (e.g., react, vue) +import { render } from '@testing-library/your-testing-library'; + +// Adjust the import based on the supported framework or Storybook's testing libraries (e.g., react, vue3) +import { composeStories } from '@storybook/your-framework'; + +const compose = (entry) => { + try { + return composeStories(entry); + } catch (e) { + throw new Error( + `There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${e}`, + ); + } +}; + +function getAllStoryFiles() { + // Place the glob you want to match your stories files + const storyFiles = glob.sync( + path.join(__dirname, 'stories/**/*.{stories,story}.{js,jsx,mjs,ts,tsx}'), + ); + + return storyFiles.map((filePath) => { + const storyFile = require(filePath); + const storyDir = path.dirname(filePath); + const componentName = path.basename(filePath).replace(/\.(stories|story)\.[^/.]+$/, ''); + + return { filePath, storyFile, storyDir, componentName }; + }); +} + +describe('Stories Snapshots', () => { + getAllStoryFiles().forEach(({ storyFile, componentName }) => { + const meta = storyFile.default; + const title = meta.title || componentName; + + describe(title, () => { + const stories = Object.entries(compose(storyFile)).map(([name, story]) => ({ name, story })); + + if (stories.length <= 0) { + throw new Error( + `No stories found for this module: ${title}. Make sure there is at least one valid story for this module.`, + ); + } + + stories.forEach(({ name, story }) => { + test(name, async () => { + const mounted = render(story()); + // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. + await new Promise((resolve) => setTimeout(resolve, 1)); + // Defines the custom snapshot path location and file name + const customSnaphotPath = `./__snapshots__/${componentName}.test.js.snap`; + expect(mounted.container).toMatchSpecificSnapshot(customSnaphotPath); + }); + }); + }); + }); +}); +``` diff --git a/docs/snippets/common/individual-snapshot-tests-portable-stories.jest.ts.mdx b/docs/snippets/common/individual-snapshot-tests-portable-stories.jest.ts.mdx new file mode 100644 index 000000000000..e245ec4c1ee9 --- /dev/null +++ b/docs/snippets/common/individual-snapshot-tests-portable-stories.jest.ts.mdx @@ -0,0 +1,82 @@ +```ts +// storybook.test.ts +// Replace your-framework with one of the supported Storybook frameworks (react, vue3) +import type { Meta, StoryFn } from '@storybook/your-framework'; + +import path from "path"; +import * as glob from "glob"; + +//πŸ‘‡ Augment expect with jest-specific-snapshot +import "jest-specific-snapshot"; + +import { describe, test, expect } from "@jest/globals"; + +// Replace your-testing-library with one of the supported testing libraries (e.g., react, vue) +import { render } from '@testing-library/your-testing-library'; + +// Adjust the import based on the supported framework or Storybook's testing libraries (e.g., react, vue3) +import { composeStories } from '@storybook/your-framework'; + +type StoryFile = { + default: Meta; + [name: string]: StoryFn | Meta; +}; + +const compose = ( + entry: StoryFile +): ReturnType> => { + try { + return composeStories(entry); + } catch (e) { + throw new Error( + `There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${e}` + ); + } +}; + +function getAllStoryFiles() { + // Place the glob you want to match your stories files + const storyFiles = glob.sync( + path.join(__dirname, 'stories/**/*.{stories,story}.{js,jsx,mjs,ts,tsx}'), + ); + + return storyFiles.map((filePath) => { + const storyFile = require(filePath); + const storyDir = path.dirname(filePath); + const componentName = path + .basename(filePath) + .replace(/\.(stories|story)\.[^/.]+$/, ""); + + return { filePath, storyFile, storyDir, componentName }; + }); +} + +describe("Stories Snapshots", () => { + getAllStoryFiles().forEach(({ storyFile, componentName }) => { + const meta = storyFile.default; + const title = meta.title || componentName; + + describe(title, () => { + const stories = Object.entries(compose(storyFile)).map( + ([name, story]) => ({ name, story }) + ); + + if (stories.length <= 0) { + throw new Error( + `No stories found for this module: ${title}. Make sure there is at least one valid story for this module.` + ); + } + + stories.forEach(({ name, story }) => { + test(name, async () => { + const mounted = render(story()); + // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. + await new Promise((resolve) => setTimeout(resolve, 1)); + // Defines the custom snapshot path location and file name + const customSnaphotPath = `./__snapshots__/${componentName}.test.ts.snap`; + expect(mounted.container).toMatchSpecificSnapshot(customSnaphotPath); + }); + }); + }); +}); +``` diff --git a/docs/snippets/common/individual-snapshot-tests-portable-stories.vitest.js.mdx b/docs/snippets/common/individual-snapshot-tests-portable-stories.vitest.js.mdx new file mode 100644 index 000000000000..111149d958bb --- /dev/null +++ b/docs/snippets/common/individual-snapshot-tests-portable-stories.vitest.js.mdx @@ -0,0 +1,64 @@ +```js +// storybook.test.js +// @vitest-environment jsdom + +import path from 'path'; +import { describe, expect, test } from 'vitest'; + +// Replace your-testing-library with one of the supported testing libraries (e.g., react, vue) +import { render } from '@testing-library/your-testing-library'; + +// Adjust the import based on the supported framework or Storybook's testing libraries (e.g., react, vue3) +import { composeStories } from '@storybook/your-framework'; + +const compose = (entry) => { + try { + return composeStories(entry); + } catch (error) { + throw new Error( + `There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${error}`, + ); + } +}; +function getAllStoryFiles() { + // Place the glob you want to match your story files + const storyFiles = Object.entries( + import.meta.glob('./stories/**/*.(stories|story).@(js|jsx|mjs|ts|tsx)', { + eager: true, + }), + ); + + return storyFiles.map(([filePath, storyFile]) => { + const storyDir = path.dirname(filePath); + const componentName = path.basename(filePath).replace(/\.(stories|story)\.[^/.]+$/, ''); + return { filePath, storyFile, componentName, storyDir }; + }); +} +describe('Stories Snapshots', () => { + getAllStoryFiles().forEach(({ storyFile, componentName }) => { + const meta = storyFile.default; + const title = meta.title || componentName; + + describe(title, () => { + const stories = Object.entries(compose(storyFile)).map(([name, story]) => ({ name, story })); + + if (stories.length <= 0) { + throw new Error( + `No stories found for this module: ${title}. Make sure there is at least one valid story for this module.`, + ); + } + + stories.forEach(({ name, story }) => { + test(name, async () => { + const mounted = render(story()); + // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. + await new Promise((resolve) => setTimeout(resolve, 1)); + // Defines the custom snapshot path location and file name + const customSnaphotPath = `./__snapshots__/${componentName}.spec.js.snap`; + expect(mounted.container).toMatchFileSnapshot(customSnaphotPath); + }); + }); + }); + }); +}); +``` diff --git a/docs/snippets/common/individual-snapshot-tests-portable-stories.vitest.ts.mdx b/docs/snippets/common/individual-snapshot-tests-portable-stories.vitest.ts.mdx new file mode 100644 index 000000000000..5c3f6097dae8 --- /dev/null +++ b/docs/snippets/common/individual-snapshot-tests-portable-stories.vitest.ts.mdx @@ -0,0 +1,74 @@ +```ts +// storybook.test.ts +// @vitest-environment jsdom + +// Replace your-framework with one of the supported Storybook frameworks (react, vue3) +import type { Meta, StoryFn } from '@storybook/your-framework'; + +import path from 'path'; +import { describe, expect, test } from 'vitest'; + +// Replace your-testing-library with one of the supported testing libraries (e.g., react, vue) +import { render } from '@testing-library/your-testing-library'; + +// Adjust the import based on the supported framework or Storybook's testing libraries (e.g., react, vue3) +import { composeStories } from '@storybook/your-framework'; + +type StoryFile = { + default: Meta; + [name: string]: StoryFn | Meta; +}; + +const compose = (entry: StoryFile): ReturnType> => { + try { + return composeStories(entry); + } catch (e) { + throw new Error( + `There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${e}`, + ); + } +}; + +function getAllStoryFiles() { + // Place the glob you want to match your story files + const storyFiles = Object.entries( + import.meta.glob('./stories/**/*.(stories|story).@(js|jsx|mjs|ts|tsx)', { + eager: true, + }), + ); + + return storyFiles.map(([filePath, storyFile]) => { + const storyDir = path.dirname(filePath); + const componentName = path.basename(filePath).replace(/\.(stories|story)\.[^/.]+$/, ''); + return { filePath, storyFile, componentName, storyDir }; + }); +} + +describe('Stories Snapshots', () => { + getAllStoryFiles().forEach(({ storyFile, componentName }) => { + const meta = storyFile.default; + const title = meta.title || componentName; + + describe(title, () => { + const stories = Object.entries(compose(storyFile)).map(([name, story]) => ({ name, story })); + + if (stories.length <= 0) { + throw new Error( + `No stories found for this module: ${title}. Make sure there is at least one valid story for this module.`, + ); + } + + stories.forEach(({ name, story }) => { + test(name, async () => { + const mounted = render(story()); + // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. + await new Promise((resolve) => setTimeout(resolve, 1)); + // Defines the custom snapshot path location and file name + const customSnaphotPath = `./__snapshots__/${componentName}.spec.ts.snap`; + expect(mounted.container).toMatchFileSnapshot(customSnaphotPath); + }); + }); + }); + }); +}); +``` diff --git a/docs/snippets/common/init-command-specific-version.npx.js.mdx b/docs/snippets/common/init-command-specific-version.npx.js.mdx deleted file mode 100644 index fd10b63fb0ce..000000000000 --- a/docs/snippets/common/init-command-specific-version.npx.js.mdx +++ /dev/null @@ -1,3 +0,0 @@ -```shell -npx storybook@7.6.6 init -``` diff --git a/docs/snippets/common/init-command-specific-version.pnpm.js.mdx b/docs/snippets/common/init-command-specific-version.pnpm.js.mdx deleted file mode 100644 index 351ed4eda295..000000000000 --- a/docs/snippets/common/init-command-specific-version.pnpm.js.mdx +++ /dev/null @@ -1,3 +0,0 @@ -```shell -pnpm dlx storybook@7.6.6 init -``` diff --git a/docs/snippets/common/init-command-specific-version.yarn.js.mdx b/docs/snippets/common/init-command-specific-version.yarn.js.mdx deleted file mode 100644 index af093e4f96dd..000000000000 --- a/docs/snippets/common/init-command-specific-version.yarn.js.mdx +++ /dev/null @@ -1,3 +0,0 @@ -```shell -yarn dlx storybook@7.6.6 init -``` diff --git a/docs/snippets/common/main-config-vite-final-env.js.mdx b/docs/snippets/common/main-config-vite-final-env.js.mdx index 73926f408471..e1ddc08d9644 100644 --- a/docs/snippets/common/main-config-vite-final-env.js.mdx +++ b/docs/snippets/common/main-config-vite-final-env.js.mdx @@ -1,14 +1,14 @@ ```js // .storybook/main.js|ts -import { mergeConfig } from 'vite'; - export default { stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], core: { builder: '@storybook/builder-vite', }, async viteFinal(config, { configType }) { + const { mergeConfig } = await import('vite'); + if (configType === 'DEVELOPMENT') { // Your development configuration goes here } diff --git a/docs/snippets/common/main-config-vite-final.js.mdx b/docs/snippets/common/main-config-vite-final.js.mdx index b2987dcd412c..dbb5d70555ba 100644 --- a/docs/snippets/common/main-config-vite-final.js.mdx +++ b/docs/snippets/common/main-config-vite-final.js.mdx @@ -1,13 +1,13 @@ ```js // .storybook/main.js -import { mergeConfig } from 'vite'; - export default { // Replace your-framework with the framework you are using (e.g., react-vite, vue3-vite) framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], async viteFinal(config, { configType }) { + const { mergeConfig } = await import('vite'); + if (configType === 'DEVELOPMENT') { // Your development configuration goes here } diff --git a/docs/snippets/common/main-config-vite-final.ts-4-9.mdx b/docs/snippets/common/main-config-vite-final.ts-4-9.mdx index 42d7f8cf8b9d..f5f36cdafdba 100644 --- a/docs/snippets/common/main-config-vite-final.ts-4-9.mdx +++ b/docs/snippets/common/main-config-vite-final.ts-4-9.mdx @@ -4,13 +4,13 @@ // Replace your-framework with the framework you are using (e.g., react-vite, vue3-vite) import type { StorybookConfig } from '@storybook/your-framework'; -import { mergeConfig } from 'vite'; - const config: StorybookConfig = { // Replace your-framework with the framework you are using (e.g., react-vite, vue3-vite) framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], async viteFinal(config, { configType }) { + const { mergeConfig } = await import('vite'); + if (configType === 'DEVELOPMENT') { // Your development configuration goes here } diff --git a/docs/snippets/common/main-config-vite-final.ts.mdx b/docs/snippets/common/main-config-vite-final.ts.mdx index a82973f98afc..2df5faf42cd5 100644 --- a/docs/snippets/common/main-config-vite-final.ts.mdx +++ b/docs/snippets/common/main-config-vite-final.ts.mdx @@ -3,12 +3,13 @@ // Replace your-framework with the framework you are using (e.g., react-vite, vue3-vite) import type { StorybookConfig } from '@storybook/your-framework'; -import { mergeConfig } from 'vite'; const config = { framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], async viteFinal(config, { configType }) { + const { mergeConfig } = await import('vite'); + if (configType === 'DEVELOPMENT') { // Your development configuration goes here } diff --git a/docs/snippets/common/snapshot-tests-portable-stories.jest.js.mdx b/docs/snippets/common/snapshot-tests-portable-stories.jest.js.mdx new file mode 100644 index 000000000000..eddd82a08227 --- /dev/null +++ b/docs/snippets/common/snapshot-tests-portable-stories.jest.js.mdx @@ -0,0 +1,64 @@ +```js +// storybook.test.js +import path from 'path'; +import * as glob from 'glob'; + +import { describe, test, expect } from '@jest/globals'; + +// Replace your-testing-library with one of the supported testing libraries (e.g., react, vue) +import { render } from '@testing-library/your-testing-library'; + +// Adjust the import based on the supported framework or Storybook's testing libraries (e.g., react, vue3) +import { composeStories } from '@storybook/your-framework'; + +const compose = (entry) => { + try { + return composeStories(entry); + } catch (e) { + throw new Error( + `There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${e}`, + ); + } +}; + +function getAllStoryFiles() { + // Place the glob you want to match your stories files + const storyFiles = glob.sync( + path.join(__dirname, 'stories/**/*.{stories,story}.{js,jsx,mjs,ts,tsx}'), + ); + + return storyFiles.map((filePath) => { + const storyFile = require(filePath); + const storyDir = path.dirname(filePath); + const componentName = path.basename(filePath).replace(/\.(stories|story)\.[^/.]+$/, ''); + + return { filePath, storyFile, storyDir, componentName }; + }); +} + +describe('Stories Snapshots', () => { + getAllStoryFiles().forEach(({ storyFile, componentName }) => { + const meta = storyFile.default; + const title = meta.title || componentName; + + describe(title, () => { + const stories = Object.entries(compose(storyFile)).map(([name, story]) => ({ name, story })); + + if (stories.length <= 0) { + throw new Error( + `No stories found for this module: ${title}. Make sure there is at least one valid story for this module.`, + ); + } + + stories.forEach(({ name, story }) => { + test(name, async () => { + const mounted = render(story()); + // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. + await new Promise((resolve) => setTimeout(resolve, 1)); + expect(mounted.container).toMatchSnapshot(); + }); + }); + }); + }); +}); +``` diff --git a/docs/snippets/common/snapshot-tests-portable-stories.jest.ts.mdx b/docs/snippets/common/snapshot-tests-portable-stories.jest.ts.mdx new file mode 100644 index 000000000000..1cfbe1bdbb35 --- /dev/null +++ b/docs/snippets/common/snapshot-tests-portable-stories.jest.ts.mdx @@ -0,0 +1,72 @@ +```ts +// storybook.test.ts +// Replace your-framework with one of the supported Storybook frameworks (react, vue3) +import type { Meta, StoryFn } from '@storybook/your-framework'; + +import path from 'path'; +import * as glob from 'glob'; + +import { describe, test, expect } from '@jest/globals'; + +// Replace your-testing-library with one of the supported testing libraries (e.g., react, vue) +import { render } from '@testing-library/your-testing-library'; + +// Adjust the import based on the supported framework or Storybook's testing libraries (e.g., react, vue3) +import { composeStories } from '@storybook/your-framework'; + +type StoryFile = { + default: Meta; + [name: string]: StoryFn | Meta; +}; + +const compose = (entry: StoryFile): ReturnType> => { + try { + return composeStories(entry); + } catch (e) { + throw new Error( + `There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${e}`, + ); + } +}; + +function getAllStoryFiles() { + // Place the glob you want to match your stories files + const storyFiles = glob.sync( + path.join(__dirname, 'stories/**/*.{stories,story}.{js,jsx,mjs,ts,tsx}'), + ); + + return storyFiles.map((filePath) => { + const storyFile = require(filePath); + const storyDir = path.dirname(filePath); + const componentName = path.basename(filePath).replace(/\.(stories|story)\.[^/.]+$/, ''); + + return { filePath, storyFile, storyDir, componentName }; + }); +} + +describe('Stories Snapshots', () => { + getAllStoryFiles().forEach(({ storyFile, componentName }) => { + const meta = storyFile.default; + const title = meta.title || componentName; + + describe(title, () => { + const stories = Object.entries(compose(storyFile)).map(([name, story]) => ({ name, story })); + + if (stories.length <= 0) { + throw new Error( + `No stories found for this module: ${title}. Make sure there is at least one valid story for this module.`, + ); + } + + stories.forEach(({ name, story }) => { + test(name, async () => { + const mounted = render(story()); + // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. + await new Promise((resolve) => setTimeout(resolve, 1)); + expect(mounted.container).toMatchSnapshot(); + }); + }); + }); + }); +}); +``` diff --git a/docs/snippets/common/snapshot-tests-portable-stories.vitest.js.mdx b/docs/snippets/common/snapshot-tests-portable-stories.vitest.js.mdx new file mode 100644 index 000000000000..dbaa397f1bb3 --- /dev/null +++ b/docs/snippets/common/snapshot-tests-portable-stories.vitest.js.mdx @@ -0,0 +1,62 @@ +```js +// storybook.test.js +// @vitest-environment jsdom + +import path from 'path'; +import { describe, expect, test } from 'vitest'; + +// Replace your-testing-library with one of the supported testing libraries (e.g., react, vue) +import { render } from '@testing-library/your-testing-library'; + +// Adjust the import based on the supported framework or Storybook's testing libraries (e.g., react, vue3) +import { composeStories } from '@storybook/your-framework'; + +const compose = (entry) => { + try { + return composeStories(entry); + } catch (error) { + throw new Error( + `There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${error}`, + ); + } +}; +function getAllStoryFiles() { + // Place the glob you want to match your story files + const storyFiles = Object.entries( + import.meta.glob('./stories/**/*.(stories|story).@(js|jsx|mjs|ts|tsx)', { + eager: true, + }), + ); + + return storyFiles.map(([filePath, storyFile]) => { + const storyDir = path.dirname(filePath); + const componentName = path.basename(filePath).replace(/\.(stories|story)\.[^/.]+$/, ''); + return { filePath, storyFile, componentName, storyDir }; + }); +} +describe('Stories Snapshots', () => { + getAllStoryFiles().forEach(({ storyFile, componentName }) => { + const meta = storyFile.default; + const title = meta.title || componentName; + + describe(title, () => { + const stories = Object.entries(compose(storyFile)).map(([name, story]) => ({ name, story })); + + if (stories.length <= 0) { + throw new Error( + `No stories found for this module: ${title}. Make sure there is at least one valid story for this module.`, + ); + } + + stories.forEach(({ name, story }) => { + test(name, async () => { + const mounted = render(story()); + // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. + await new Promise((resolve) => setTimeout(resolve, 1)); + expect(mounted.container).toMatchSnapshot(); + }); + }); + }); + }); +}); +``` diff --git a/docs/snippets/common/snapshot-tests-portable-stories.vitest.ts.mdx b/docs/snippets/common/snapshot-tests-portable-stories.vitest.ts.mdx new file mode 100644 index 000000000000..456bf5a1ae9d --- /dev/null +++ b/docs/snippets/common/snapshot-tests-portable-stories.vitest.ts.mdx @@ -0,0 +1,72 @@ +```ts +// storybook.test.ts +// @vitest-environment jsdom + +// Replace your-framework with one of the supported Storybook frameworks (react, vue3) +import type { Meta, StoryFn } from '@storybook/your-framework'; + +import path from 'path'; +import { describe, expect, test } from 'vitest'; + +// Replace your-testing-library with one of the supported testing libraries (e.g., react, vue) +import { render } from '@testing-library/your-testing-library'; + +// Adjust the import based on the supported framework or Storybook's testing libraries (e.g., react, vue3) +import { composeStories } from '@storybook/your-framework'; + +type StoryFile = { + default: Meta; + [name: string]: StoryFn | Meta; +}; + +const compose = (entry: StoryFile): ReturnType> => { + try { + return composeStories(entry); + } catch (e) { + throw new Error( + `There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${e}`, + ); + } +}; + +function getAllStoryFiles() { + // Place the glob you want to match your story files + const storyFiles = Object.entries( + import.meta.glob('./stories/**/*.(stories|story).@(js|jsx|mjs|ts|tsx)', { + eager: true, + }), + ); + + return storyFiles.map(([filePath, storyFile]) => { + const storyDir = path.dirname(filePath); + const componentName = path.basename(filePath).replace(/\.(stories|story)\.[^/.]+$/, ''); + return { filePath, storyFile, componentName, storyDir }; + }); +} + +describe('Stories Snapshots', () => { + getAllStoryFiles().forEach(({ storyFile, componentName }) => { + const meta = storyFile.default; + const title = meta.title || componentName; + + describe(title, () => { + const stories = Object.entries(compose(storyFile)).map(([name, story]) => ({ name, story })); + + if (stories.length <= 0) { + throw new Error( + `No stories found for this module: ${title}. Make sure there is at least one valid story for this module.`, + ); + } + + stories.forEach(({ name, story }) => { + test(name, async () => { + const mounted = render(story()); + // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. + await new Promise((resolve) => setTimeout(resolve, 1)); + expect(mounted.container).toMatchSnapshot(); + }); + }); + }); + }); +}); +``` diff --git a/docs/snippets/common/storybook-init-v7.npx.js.mdx b/docs/snippets/common/storybook-init-v7.npx.js.mdx new file mode 100644 index 000000000000..33a0a5b19737 --- /dev/null +++ b/docs/snippets/common/storybook-init-v7.npx.js.mdx @@ -0,0 +1,3 @@ +```shell +npx storybook@^7 init +``` diff --git a/docs/snippets/common/storybook-init-v7.pnpm.js.mdx b/docs/snippets/common/storybook-init-v7.pnpm.js.mdx new file mode 100644 index 000000000000..19ae54c8d049 --- /dev/null +++ b/docs/snippets/common/storybook-init-v7.pnpm.js.mdx @@ -0,0 +1,3 @@ +```shell +pnpm dlx storybook@^7 init +``` diff --git a/docs/snippets/common/storybook-init-v7.yarn.js.mdx b/docs/snippets/common/storybook-init-v7.yarn.js.mdx new file mode 100644 index 000000000000..8001609fe3ca --- /dev/null +++ b/docs/snippets/common/storybook-init-v7.yarn.js.mdx @@ -0,0 +1,3 @@ +```shell +yarn dlx storybook@^7 init +``` diff --git a/docs/snippets/common/storybook-vite-builder-aliasing.js.mdx b/docs/snippets/common/storybook-vite-builder-aliasing.js.mdx index ea5f2e7550de..c6dfb19d7581 100644 --- a/docs/snippets/common/storybook-vite-builder-aliasing.js.mdx +++ b/docs/snippets/common/storybook-vite-builder-aliasing.js.mdx @@ -1,8 +1,6 @@ ```js // .storybook/main.js|ts -import { mergeConfig } from 'vite'; - export default { stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], addons: ['@storybook/addon-links', '@storybook/addon-essentials'], @@ -11,6 +9,8 @@ export default { }, async viteFinal(config) { // Merge custom configuration into the default config + const { mergeConfig } = await import('vite'); + return mergeConfig(config, { // Add dependencies to pre-optimization optimizeDeps: { diff --git a/docs/snippets/common/test-runner-snapshot-resolver-custom-directory.js.mdx b/docs/snippets/common/test-runner-snapshot-resolver-custom-directory.js.mdx new file mode 100644 index 000000000000..203c8d30cbb2 --- /dev/null +++ b/docs/snippets/common/test-runner-snapshot-resolver-custom-directory.js.mdx @@ -0,0 +1,19 @@ +```js +// ./snapshot-resolver.js +import path from 'path'; + +export default { + resolveSnapshotPath: (testPath) => { + const fileName = path.basename(testPath); + const fileNameWithoutExtension = fileName.replace(/\.[^/.]+$/, ''); + // Defines the file extension for the snapshot file + const modifiedFileName = `${fileNameWithoutExtension}.snap`; + + // Configure Jest to generate snapshot files using the following convention (./src/test/__snapshots__/Button.stories.snap) + return path.join('./src/test/__snapshots__', modifiedFileName); + }, + resolveTestPath: (snapshotFilePath, snapshotExtension) => + path.basename(snapshotFilePath, snapshotExtension), + testPathForConsistencyCheck: 'example', +}; +``` diff --git a/docs/snippets/react/button-snapshot-test-portable-stories.jest.js.mdx b/docs/snippets/react/button-snapshot-test-portable-stories.jest.js.mdx new file mode 100644 index 000000000000..335eb461d736 --- /dev/null +++ b/docs/snippets/react/button-snapshot-test-portable-stories.jest.js.mdx @@ -0,0 +1,14 @@ +```js +// test/Button.test.js|ts +import { render } from '@testing-library/react'; + +import { composeStories } from '@storybook/react'; + +import * as stories from '../stories/Button.stories'; + +const { Primary } = composeStories(stories); +test('Button snapshot', async () => { + const mounted = render(); + expect(mounted.container).toMatchSnapshot(); +}); +``` diff --git a/docs/snippets/react/button-snapshot-test-portable-stories.vitest.js.mdx b/docs/snippets/react/button-snapshot-test-portable-stories.vitest.js.mdx new file mode 100644 index 000000000000..982992cc65aa --- /dev/null +++ b/docs/snippets/react/button-snapshot-test-portable-stories.vitest.js.mdx @@ -0,0 +1,18 @@ +```js +// test/Button.test.js|ts +// @vitest-environment jsdom + +import { expect, test } from 'vitest'; + +import { render } from '@testing-library/react'; + +import { composeStories } from '@storybook/react'; + +import * as stories from '../stories/Button.stories'; + +const { Primary } = composeStories(stories); +test('Button snapshot', async () => { + const mounted = render(Primary()); + expect(mounted.container).toMatchSnapshot(); +}); +``` diff --git a/docs/snippets/react/react-vite-add-framework.js.mdx b/docs/snippets/react/react-vite-add-framework.js.mdx new file mode 100644 index 000000000000..7a268b5768f0 --- /dev/null +++ b/docs/snippets/react/react-vite-add-framework.js.mdx @@ -0,0 +1,8 @@ +```js +// .storybook/main.js +export default { + // ... + // framework: '@storybook/react-webpack5', πŸ‘ˆ Remove this + framework: '@storybook/react-vite', // πŸ‘ˆ Add this +}; +``` diff --git a/docs/snippets/react/react-vite-add-framework.ts.mdx b/docs/snippets/react/react-vite-add-framework.ts.mdx new file mode 100644 index 000000000000..c407f12cc0a7 --- /dev/null +++ b/docs/snippets/react/react-vite-add-framework.ts.mdx @@ -0,0 +1,12 @@ +```ts +// .storybook/main.ts +import { StorybookConfig } from '@storybook/react-vite'; + +const config: StorybookConfig = { + // ... + // framework: '@storybook/react-webpack5', πŸ‘ˆ Remove this + framework: '@storybook/react-vite', // πŸ‘ˆ Add this +}; + +export default config; +``` diff --git a/docs/snippets/react/react-vite-install.npm.js.mdx b/docs/snippets/react/react-vite-install.npm.js.mdx new file mode 100644 index 000000000000..6a9b052c19bb --- /dev/null +++ b/docs/snippets/react/react-vite-install.npm.js.mdx @@ -0,0 +1,3 @@ +```shell +npm install --save-dev @storybook/react-vite +``` diff --git a/docs/snippets/react/react-vite-install.pnpm.js.mdx b/docs/snippets/react/react-vite-install.pnpm.js.mdx new file mode 100644 index 000000000000..10b128bb0b31 --- /dev/null +++ b/docs/snippets/react/react-vite-install.pnpm.js.mdx @@ -0,0 +1,3 @@ +```shell +pnpm install --save-dev @storybook/react-vite +``` diff --git a/docs/snippets/react/react-vite-install.yarn.js.mdx b/docs/snippets/react/react-vite-install.yarn.js.mdx new file mode 100644 index 000000000000..a566adef61ba --- /dev/null +++ b/docs/snippets/react/react-vite-install.yarn.js.mdx @@ -0,0 +1,3 @@ +```shell +yarn add --dev @storybook/react-vite +``` diff --git a/docs/snippets/react/react-webpack5-add-framework.js.mdx b/docs/snippets/react/react-webpack5-add-framework.js.mdx new file mode 100644 index 000000000000..cbacf99bd80a --- /dev/null +++ b/docs/snippets/react/react-webpack5-add-framework.js.mdx @@ -0,0 +1,7 @@ +```js +// .storybook/main.js +export default { + // ... + framework: '@storybook/react-webpack5', // πŸ‘ˆ Add this +}; +``` diff --git a/docs/snippets/react/react-webpack5-add-framework.ts.mdx b/docs/snippets/react/react-webpack5-add-framework.ts.mdx new file mode 100644 index 000000000000..2417fd5ea98a --- /dev/null +++ b/docs/snippets/react/react-webpack5-add-framework.ts.mdx @@ -0,0 +1,11 @@ +```ts +// .storybook/main.ts +import { StorybookConfig } from '@storybook/react-webpack5'; + +const config: StorybookConfig = { + // ... + framework: '@storybook/react-webpack5', // πŸ‘ˆ Add this +}; + +export default config; +``` diff --git a/docs/snippets/react/react-webpack5-install.npm.js.mdx b/docs/snippets/react/react-webpack5-install.npm.js.mdx new file mode 100644 index 000000000000..8b54052f3f28 --- /dev/null +++ b/docs/snippets/react/react-webpack5-install.npm.js.mdx @@ -0,0 +1,3 @@ +```shell +npm install --save-dev @storybook/react-webpack5 +``` diff --git a/docs/snippets/react/react-webpack5-install.pnpm.js.mdx b/docs/snippets/react/react-webpack5-install.pnpm.js.mdx new file mode 100644 index 000000000000..840d64ae06c9 --- /dev/null +++ b/docs/snippets/react/react-webpack5-install.pnpm.js.mdx @@ -0,0 +1,3 @@ +```shell +pnpm install --save-dev @storybook/react-webpack5 +``` diff --git a/docs/snippets/react/react-webpack5-install.yarn.js.mdx b/docs/snippets/react/react-webpack5-install.yarn.js.mdx new file mode 100644 index 000000000000..b4f143c8a120 --- /dev/null +++ b/docs/snippets/react/react-webpack5-install.yarn.js.mdx @@ -0,0 +1,3 @@ +```shell +yarn add --dev @storybook/react-webpack5 +``` diff --git a/docs/snippets/react/storybook-testing-addon-optional-config.vite.js.mdx b/docs/snippets/react/storybook-testing-addon-optional-config.vite.js.mdx new file mode 100644 index 000000000000..a50bd78cb49b --- /dev/null +++ b/docs/snippets/react/storybook-testing-addon-optional-config.vite.js.mdx @@ -0,0 +1,20 @@ +```js +// vitest.config.js + +import { defineConfig } from 'vitest/config'; +import { mergeConfig } from 'vite'; + +import viteConfig from './vite.config'; + +export default mergeConfig( + viteConfig, + defineConfig({ + test: { + globals: true, + environment: 'jsdom', + clearMocks: true, + setupFiles: './src/setupTests.js', //πŸ‘ˆ Our configuration file enabled here + }, + }), +); +``` diff --git a/docs/snippets/react/storybook-testing-addon-optional-config.vite.ts.mdx b/docs/snippets/react/storybook-testing-addon-optional-config.vite.ts.mdx new file mode 100644 index 000000000000..3161eb45448c --- /dev/null +++ b/docs/snippets/react/storybook-testing-addon-optional-config.vite.ts.mdx @@ -0,0 +1,21 @@ +```ts +// vitest.config.ts + +/// +import { defineConfig } from 'vitest/config'; +import { mergeConfig } from 'vite'; + +import viteConfig from './vite.config'; + +export default mergeConfig( + viteConfig, + defineConfig({ + test: { + globals: true, + environment: 'jsdom', + clearMocks: true, + setupFiles: './src/setupTests.ts', //πŸ‘ˆ Our configuration file enabled here + }, + }), +); +``` diff --git a/docs/snippets/svelte/svelte-csf-addon-install.npm.js.mdx b/docs/snippets/svelte/svelte-csf-addon-install.npm.js.mdx index 574d63651863..3889ca8909c4 100644 --- a/docs/snippets/svelte/svelte-csf-addon-install.npm.js.mdx +++ b/docs/snippets/svelte/svelte-csf-addon-install.npm.js.mdx @@ -1,3 +1,3 @@ ```shell -npm install @storybook/addon-svelte-csf --save-dev +npx storybook@latest add @storybook/addon-svelte-csf ``` diff --git a/docs/snippets/svelte/svelte-csf-addon-install.pnpm.js.mdx b/docs/snippets/svelte/svelte-csf-addon-install.pnpm.js.mdx index 4ff9be66f107..eb8c09e0708c 100644 --- a/docs/snippets/svelte/svelte-csf-addon-install.pnpm.js.mdx +++ b/docs/snippets/svelte/svelte-csf-addon-install.pnpm.js.mdx @@ -1,3 +1,3 @@ ```shell -pnpm add --save-dev @storybook/addon-svelte-csf +pnpm dlx storybook@latest add @storybook/addon-svelte-csf ``` diff --git a/docs/snippets/svelte/svelte-csf-addon-install.yarn.js.mdx b/docs/snippets/svelte/svelte-csf-addon-install.yarn.js.mdx index 5b3e6dff23f6..52ecff831d4c 100644 --- a/docs/snippets/svelte/svelte-csf-addon-install.yarn.js.mdx +++ b/docs/snippets/svelte/svelte-csf-addon-install.yarn.js.mdx @@ -1,3 +1,3 @@ ```shell -yarn add --dev @storybook/addon-svelte-csf +yarn storybook@latest add @storybook/addon-svelte-csf ``` diff --git a/docs/snippets/svelte/svelte-vite-add-framework.js.mdx b/docs/snippets/svelte/svelte-vite-add-framework.js.mdx new file mode 100644 index 000000000000..a16370254866 --- /dev/null +++ b/docs/snippets/svelte/svelte-vite-add-framework.js.mdx @@ -0,0 +1,7 @@ +```js +// .storybook/main.js +export default { + // ... + framework: '@storybook/svelte-vite', // πŸ‘ˆ Add this +}; +``` diff --git a/docs/snippets/svelte/svelte-vite-add-framework.ts.mdx b/docs/snippets/svelte/svelte-vite-add-framework.ts.mdx new file mode 100644 index 000000000000..920d91ae6d13 --- /dev/null +++ b/docs/snippets/svelte/svelte-vite-add-framework.ts.mdx @@ -0,0 +1,11 @@ +```ts +// .storybook/main.ts +import { StorybookConfig } from '@storybook/svelte-vite'; + +const config: StorybookConfig = { + // ... + framework: '@storybook/svelte-vite', // πŸ‘ˆ Add this +}; + +export default config; +``` diff --git a/docs/snippets/svelte/svelte-vite-install.npm.js.mdx b/docs/snippets/svelte/svelte-vite-install.npm.js.mdx new file mode 100644 index 000000000000..8b6986b5ecfa --- /dev/null +++ b/docs/snippets/svelte/svelte-vite-install.npm.js.mdx @@ -0,0 +1,3 @@ +```shell +npm install --save-dev @storybook/svelte-vite +``` diff --git a/docs/snippets/svelte/svelte-vite-install.pnpm.js.mdx b/docs/snippets/svelte/svelte-vite-install.pnpm.js.mdx new file mode 100644 index 000000000000..585bc12393ca --- /dev/null +++ b/docs/snippets/svelte/svelte-vite-install.pnpm.js.mdx @@ -0,0 +1,3 @@ +```shell +pnpm install --save-dev @storybook/svelte-vite +``` diff --git a/docs/snippets/svelte/svelte-vite-install.yarn.js.mdx b/docs/snippets/svelte/svelte-vite-install.yarn.js.mdx new file mode 100644 index 000000000000..7a8b11a4d500 --- /dev/null +++ b/docs/snippets/svelte/svelte-vite-install.yarn.js.mdx @@ -0,0 +1,3 @@ +```shell +yarn add --dev @storybook/svelte-vite +``` diff --git a/docs/snippets/vue/button-snapshot-test-portable-stories.js.mdx b/docs/snippets/vue/button-snapshot-test-portable-stories.js.mdx new file mode 100644 index 000000000000..06d28200e454 --- /dev/null +++ b/docs/snippets/vue/button-snapshot-test-portable-stories.js.mdx @@ -0,0 +1,18 @@ +```js +// __tests__/Button.spec.js|ts +// @vitest-environment jsdom + +import { expect, test } from 'vitest'; + +import { render } from '@testing-library/vue'; + +import { composeStories } from '@storybook/vue3'; + +import * as stories from '../stories/Button.stories'; + +const { Primary } = composeStories(stories); +test('Button snapshot', async () => { + const mounted = render(Primary()); + expect(mounted.container).toMatchSnapshot(); +}); +``` diff --git a/docs/snippets/vue/vue3-vite-add-framework.js.mdx b/docs/snippets/vue/vue3-vite-add-framework.js.mdx new file mode 100644 index 000000000000..009f6f74b579 --- /dev/null +++ b/docs/snippets/vue/vue3-vite-add-framework.js.mdx @@ -0,0 +1,7 @@ +```js +// .storybook/main.js +export default { + // ... + framework: '@storybook/vue3-vite', // πŸ‘ˆ Add this +}; +``` diff --git a/docs/snippets/vue/vue3-vite-add-framework.ts.mdx b/docs/snippets/vue/vue3-vite-add-framework.ts.mdx new file mode 100644 index 000000000000..c02ed2ed0b3b --- /dev/null +++ b/docs/snippets/vue/vue3-vite-add-framework.ts.mdx @@ -0,0 +1,11 @@ +```ts +// .storybook/main.ts +import { StorybookConfig } from '@storybook/nextjs'; + +const config: StorybookConfig = { + // ... + framework: '@storybook/vue3-vite', // πŸ‘ˆ Add this +}; + +export default config; +``` diff --git a/docs/snippets/vue/vue3-vite-install.npm.js.mdx b/docs/snippets/vue/vue3-vite-install.npm.js.mdx new file mode 100644 index 000000000000..1d023d6e253c --- /dev/null +++ b/docs/snippets/vue/vue3-vite-install.npm.js.mdx @@ -0,0 +1,3 @@ +```shell +npm install --save-dev @storybook/vue3-vite +``` diff --git a/docs/snippets/vue/vue3-vite-install.pnpm.js.mdx b/docs/snippets/vue/vue3-vite-install.pnpm.js.mdx new file mode 100644 index 000000000000..3c8db13088d7 --- /dev/null +++ b/docs/snippets/vue/vue3-vite-install.pnpm.js.mdx @@ -0,0 +1,3 @@ +```shell +pnpm install --save-dev @storybook/vue3-vite +``` diff --git a/docs/snippets/vue/vue3-vite-install.yarn.js.mdx b/docs/snippets/vue/vue3-vite-install.yarn.js.mdx new file mode 100644 index 000000000000..427eb154d182 --- /dev/null +++ b/docs/snippets/vue/vue3-vite-install.yarn.js.mdx @@ -0,0 +1,3 @@ +```shell +yarn add --dev @storybook/vue3-vite +``` diff --git a/docs/toc.js b/docs/toc.js index b7ee152fb8c6..5b660a93cce5 100644 --- a/docs/toc.js +++ b/docs/toc.js @@ -23,11 +23,36 @@ module.exports = { title: 'Frameworks', type: 'menu', children: [ + { + pathSegment: 'angular', + title: 'Angular', + type: 'link', + }, { pathSegment: 'nextjs', title: 'Next.js', type: 'link', }, + { + pathSegment: 'react-vite', + title: 'React & Vite', + type: 'link', + }, + { + pathSegment: 'react-webpack5', + title: 'React & Webpack', + type: 'link', + }, + { + pathSegment: 'svelte-vite', + title: 'Svelte & Vite', + type: 'link', + }, + { + pathSegment: 'vue3-vite', + title: 'Vue & Vite', + type: 'link', + }, ], }, { @@ -173,12 +198,12 @@ module.exports = { children: [ { pathSegment: 'snapshot-testing', - title: 'Storyshots', + title: 'Write', type: 'link', }, { pathSegment: 'storyshots-migration-guide', - title: 'Migration guide', + title: 'Storyshots migration guide', type: 'link', }, ], diff --git a/docs/versions/next.json b/docs/versions/next.json index a1c6e9350e50..e522ee4c40f2 100644 --- a/docs/versions/next.json +++ b/docs/versions/next.json @@ -1 +1 @@ -{"version":"8.0.0-rc.1","info":{"plain":"- CLI: Fix addon compatibility check error reporting in storybook dev - [#26258](https://github.com/storybookjs/storybook/pull/26258), thanks [@yannbf](https://github.com/yannbf)!\n- Onboarding: Fix manager dist reference - [#26282](https://github.com/storybookjs/storybook/pull/26282), thanks [@shilman](https://github.com/shilman)!\n- ReactVite: Docgen ignore un-parsable files - [#26254](https://github.com/storybookjs/storybook/pull/26254), thanks [@ndelangen](https://github.com/ndelangen)!"}} +{"version":"8.0.0-rc.5","info":{"plain":"- CLI: Automigration fix version detection of upgrading related packages - [#26410](https://github.com/storybookjs/storybook/pull/26410), thanks @ndelangen!"}} diff --git a/docs/writing-stories/naming-components-and-hierarchy.md b/docs/writing-stories/naming-components-and-hierarchy.md index 5757a2eda12a..b29b1135e2d5 100644 --- a/docs/writing-stories/naming-components-and-hierarchy.md +++ b/docs/writing-stories/naming-components-and-hierarchy.md @@ -146,12 +146,12 @@ The `storySort` can also accept a configuration object. -| Field | Type | Description | Required | Default Value | Example | -| ---------------- | :-----: | :------------------------------------------------------: | :------: | :---------------------: | :-----------------------: | -| **method** | String | Tells Storybook in which order the stories are displayed | No | Storybook configuration | `'alphabetical'` | -| **order** | Array | The stories to be shown, ordered by supplied name | No | Empty Array `[]` | `['Intro', 'Components']` | -| **includeNames** | Boolean | Include story name in sort calculation | No | `false` | `true` | -| **locales** | String | The locale required to be displayed | No | System locale | `en-US` | +| Field | Type | Description | Required | Default Value | Example | +| ---------------- | ------- | -------------------------------------------------------- | -------- | ----------------------- | ------------------------- | +| **method** | String | Tells Storybook in which order the stories are displayed | No | Storybook configuration | `'alphabetical'` | +| **order** | Array | The stories to be shown, ordered by supplied name | No | Empty Array `[]` | `['Intro', 'Components']` | +| **includeNames** | Boolean | Include story name in sort calculation | No | `false` | `true` | +| **locales** | String | The locale required to be displayed | No | System locale | `en-US` | To sort your stories alphabetically, set `method` to `'alphabetical'` and optionally set the `locales` string. To sort your stories using a custom list, use the `order` array; stories that don't match an item in the `order` list will appear after the items in the list. diff --git a/docs/writing-tests/snapshot-testing.md b/docs/writing-tests/snapshot-testing.md index 438258b36adb..36616674380d 100644 --- a/docs/writing-tests/snapshot-testing.md +++ b/docs/writing-tests/snapshot-testing.md @@ -1,5 +1,5 @@ --- -title: 'Snapshot testing with Storyshots' +title: 'Write snapshot tests' --- Snapshot tests compare the rendered markup of every story against known baselines. It’s a way to identify markup changes that trigger rendering errors and warnings. @@ -8,118 +8,195 @@ Storybook is a helpful tool for snapshot testing because every story is essentia ![Example Snapshot test](./snapshot-test.png) -## Migrating Tests + -The Storyshots addon was the original testing solution for Storybook, offering a highly extensible API and a wide range of configuration options for testing. However, it was difficult to set up and maintain, and it needed to be compatible with the latest version of Storybook, which introduced some significant architectural changes, including a high-performance [on-demand story loading](../configure/index.md#on-demand-story-loading) feature. As a result, Storyshots is now officially deprecated, is no longer being maintained, and will be removed in the next major release of Storybook. We recommend following the [migration guide](./storyshots-migration-guide.md) we've prepared to help you during this transition period. +If you're [upgrading](../configure/upgrading.md) to Storybook 8.0 and were using the Storyshots addon for snapshot testing, it was officially deprecated and removed with this release. See the [migration guide](./storyshots-migration-guide.md) for more information. -## Set up Storyshots + + +## Automate snapshot tests with the test-runner + +Storybook test-runner turns all of your stories into executable tests. Powered by [Jest](https://jestjs.io/) and [Playwright](https://playwright.dev/). It's a standalone, framework-agnostic utility that runs parallel to your Storybook. It enables you to run multiple testing patterns in a multi-browser environment, including interaction testing with the [play function](./interaction-testing.md), DOM snapshot, and [accessibility testing](./accessibility-testing.md). - +### Setup -The Storyshots addon was deprecated and has been removed in Storybook 8. See the [migration guide](./storyshots-migration-guide.md) for more information. +To enable snapshot testing with the test-runner, you'll need to take additional steps to set it up properly. We recommend you go through the [test-runner documentation](./test-runner.md) before proceeding with the rest of the required configuration to learn more about the available options and APIs. + +Add a new [configuration file](./test-runner.md#test-hook-api) inside your Storybook directory with the following inside: + + + + + + + + + +The `postVisit` hook allows you to extend the test runner's default configuration. Read more about them [here](./test-runner.md#test-hook-api). -[Storyshots](https://storybook.js.org/addons/@storybook/addon-storyshots/) is a Storybook addon that enables snapshot testing, powered by [Jest](https://jestjs.io/docs/getting-started). +When you execute the test-runner (for example, with `yarn test-storybook`), it will run through all of your stories and run the snapshot tests, generating a snapshot file for each story in your project located in the `__snapshots__` directory. + +### Configure -Run the following command to install Storyshots: +Out of the box, the test-runner provides an inbuilt snapshot testing configuration covering most use cases. You can also fine-tune the configuration to fit your needs via `test-storybook --eject` or by creating a `test-runner-jest.config.js` file at the root of your project. + +#### Override the default snapshot directory + +The test-runner uses a specific naming convention and path for the generated snapshot files by default. If you need to customize the snapshot directory, you can define a custom snapshot resolver to specify the directory where the snapshots are stored. + +Create a `snapshot-resolver.js` file to implement a custom snapshot resolver: -Add a test file to your environment with the following contents to configure Storyshots: +Update the `test-runner-jest.config.js` file and enable the `snapshotResolver` option to use the custom snapshot resolver: - +When the test-runner is executed, it will cycle through all of your stories and run the snapshot tests, generating a snapshot file for each story in your project located in the custom directory you specified. -You can name the test file differently to suit your needs. Bear in mind that it requires to be picked up by Jest. +#### Customize snapshot serialization - +By default, the test-runner uses [`jest-serializer-html`](https://github.com/algolia/jest-serializer-html) to serialize HTML snapshots. This may cause issues if you use specific CSS-in-JS libraries like [Emotion](https://emotion.sh/docs/introduction), Angular's `ng` attributes, or similar libraries that generate hash-based identifiers for CSS classes. If you need to customize the serialization of your snapshots, you can define a custom snapshot serializer to specify how the snapshots are serialized. -Run your first test. Storyshots will recognize your stories (based on [.storybook/main.js's setup](../configure/story-rendering.md)) and save them in the **snapshots** directory. +Create a `snapshot-serializer.js` file to implement a custom snapshot serializer: -```shell -npm test storybook.test.js -``` + -![Successful snapshot tests](./storyshots-pass.png) + -When you make changes to your components or stories, rerun the test to identify the changes to the rendered markup. + -![Failing snapshots](./storyshots-fail.png) +Update the `test-runner-jest.config.js` file and enable the `snapshotSerializers` option to use the custom snapshot resolver: -If they're intentional, accept them as new baselines. If the changes are bugs, fix the underlying code, then rerun the snapshot tests. + -### Configure the snapshot's directory + -If your project has a custom setup for snapshot testing, you'll need to take additional steps to run Storyshots. You'll need to install both [@storybook/addon-storyshots-puppeteer](https://storybook.js.org/addons/@storybook/addon-storyshots-puppeteer) and [puppeteer](https://github.com/puppeteer/puppeteer): + + +When the test-runner executes your tests, it will introspect the resulting HTML, replacing the dynamically generated attributes with the static ones provided by the regular expression in the custom serializer file before snapshotting the component. This ensures that the snapshots are consistent across different test runs. + + + + -```shell -# With npm -npm i -D @storybook/addon-storyshots-puppeteer puppeteer +## Snapshot tests with Portable Stories -# With yarn -yarn add @storybook/addon-storyshots-puppeteer puppeteer -``` +Storybook provides a `composeStories` utility that helps convert stories from a test file into renderable elements that can be reused in your Node tests with JSDOM. It also allows you to apply other Storybook features that you have enabled your project (e.g., [decorators](../writing-stories/decorators.md), [args](../writing-stories/args.md)) into your tests, enabling you to reuse your stories in your testing environment of choice (e.g., [Jest](https://jestjs.io/), [Vitest](https://vitest.dev/)), ensuring your tests are always in sync with your stories without having to rewrite them. This is what we refer to as portable stories in Storybook. -Next, update your test file (for example, `storybook.test.js`) to the following: +### Configure + +By default, Storybook offers a zero-config setup for React, Vue, and other frameworks via addons, allowing you to run your stories as tests with your testing environment of choice. However, if you're running tests and you've set up specific configurations in your Storybook instance (e.g., global [decorators](../writing-stories/decorators.md#global-decorators), [parameters](../writing-stories/parameters.md#global-parameters)) that you want to use in your tests, you'll need to extend your test setup to include these configurations. To do so, create a `setup.js|ts` file as follows: - +Update your test configuration file (e.g., `vite.config.js|ts`) if you're using [Vitest](https://vitest.dev/) or your test script if you're using [Jest](https://jestjs.io/): -Don't forget to replace your-custom-directory with your own. + - + -When you run your tests, the snapshots will be available in your specified directory. + -### Framework configuration +### Run tests on a single story -By default, Storyshots detects your project's framework. If you encounter a situation where this is not the case, you can adjust the configuration object and specify your framework. For example, if you wanted to configure the addon for a Vue 3 project: +If you need to run tests on a single story, you can use the `composeStories` function from the appropriate framework to process it and apply any configuration you've defined in your stories (e.g., [decorators](../writing-stories/decorators.md), [args](../writing-stories/args.md)) and combine it with your testing environment to generate a snapshot file. For example, if you're working on a component and you want to test its default state, ensuring the expected DOM structure doesn't change, here's how you could write your test: -These are the frameworks currently supported by Storyshots: `angular`, `html`, `preact`, `react`, `react-native`, `svelte`, `vue`, `vue3`, and `web-components`. +### Execute tests on multiple stories -### Additional customization +You can also use the `composeStories` function to test multiple stories. This is useful when you want to extend your test coverage to generate snapshots for the different states of the components in your project. To do so, you can write your test as follows: -Storyshots is highly customizable and includes options for various advanced use cases. You can read more in the [addon’s documentation](https://github.com/storybookjs/storybook/tree/master/addons/storyshots/storyshots-core#options). + ---- + + + + +When your tests are executed in your testing environment, they will generate a single snapshot file with all the stories in your project (i.e.,`storybook.test.ts|js.snap`). However, if you need, you can extend your test file to generate individual snapshot files for each story in your project with Vitest's [`toMatchFileSnapshot`](https://vitest.dev/guide/snapshot.html#file-snapshots) API or Jest's [`jest-specific-snapshot`](https://github.com/igor-dv/jest-specific-snapshot) package. For example: + + + + + + + + #### What’s the difference between snapshot tests and visual tests? diff --git a/docs/writing-tests/storyshots-fail.png b/docs/writing-tests/storyshots-fail.png deleted file mode 100644 index 1cf3677509f5..000000000000 Binary files a/docs/writing-tests/storyshots-fail.png and /dev/null differ diff --git a/docs/writing-tests/storyshots-pass.png b/docs/writing-tests/storyshots-pass.png deleted file mode 100644 index b93d218c8f34..000000000000 Binary files a/docs/writing-tests/storyshots-pass.png and /dev/null differ diff --git a/scripts/package.json b/scripts/package.json index af4682a9ccd9..542bca5d2fd0 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -55,7 +55,7 @@ }, "resolutions": { "@testing-library/jest-dom": "^5.11.9", - "esbuild": "^0.18.0", + "esbuild": "^0.20.1", "serialize-javascript": "^3.1.0", "type-fest": "~2.19" }, @@ -122,7 +122,7 @@ "detect-port": "^1.3.0", "ejs": "^3.1.8", "ejs-lint": "^2.0.0", - "esbuild": "^0.18.0", + "esbuild": "^0.20.1", "esbuild-plugin-alias": "^0.2.1", "esbuild-register": "^3.5.0", "eslint": "^8.56.0", diff --git a/scripts/release/generate-pr-description.ts b/scripts/release/generate-pr-description.ts index 438c13fb4a91..7b4e397dea2c 100644 --- a/scripts/release/generate-pr-description.ts +++ b/scripts/release/generate-pr-description.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import chalk from 'chalk'; import program from 'commander'; import { z } from 'zod'; @@ -65,7 +64,6 @@ export const mapToChangelist = ({ }): string => { return changes .filter((change) => { - // eslint-disable-next-line no-restricted-syntax for (const titleToIgnore of CHANGE_TITLES_TO_IGNORE) { if (change.title?.match(titleToIgnore)) { return false; @@ -227,7 +225,7 @@ export const generateNonReleaseDescription = ( - Merge this PR - [Follow the run of the publish action](https://github.com/storybookjs/storybook/actions/workflows/publish.yml)` // don't mention contributors in the release PR, to avoid spamming them - .replaceAll('[@', '[@ ') + .replaceAll('@', '') .replaceAll('"', '\\"') .replaceAll('`', '\\`') .replaceAll("'", "\\'") diff --git a/scripts/release/utils/get-changes.ts b/scripts/release/utils/get-changes.ts index 416ea624fb50..8d2811d486db 100644 --- a/scripts/release/utils/get-changes.ts +++ b/scripts/release/utils/get-changes.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import chalk from 'chalk'; import semver from 'semver'; import type { PullRequestInfo } from './get-github-info'; @@ -206,11 +205,11 @@ export const getChangelogText = ({ return entry.labels?.some((label) => Object.keys(RELEASED_LABELS).includes(label)); }) .map((entry) => { - const { title, links } = entry; - const { pull, commit, user } = links; + const { title, user, links } = entry; + const { pull, commit } = links; return pull - ? `- ${title} - ${pull}, thanks ${user}!` - : `- ⚠️ _Direct commit_ ${title} - ${commit} by ${user}`; + ? `- ${title} - ${pull}, thanks @${user}!` + : `- ⚠️ _Direct commit_ ${title} - ${commit} by @${user}`; }) .sort(); const text = [heading, '', ...formattedEntries].join('\n'); diff --git a/scripts/release/utils/get-github-info.ts b/scripts/release/utils/get-github-info.ts index bdfe995a9001..e124026d7133 100644 --- a/scripts/release/utils/get-github-info.ts +++ b/scripts/release/utils/get-github-info.ts @@ -73,7 +73,7 @@ function makeQuery(repos: ReposWithCommitsAndPRsToFetch) { nodes { name } - } + } mergeCommit { commitUrl oid diff --git a/scripts/sandbox/generate.ts b/scripts/sandbox/generate.ts index 27f17f8c7ad0..410d49373d93 100755 --- a/scripts/sandbox/generate.ts +++ b/scripts/sandbox/generate.ts @@ -27,6 +27,13 @@ import { REPROS_DIRECTORY, LOCAL_REGISTRY_URL, } from '../utils/constants'; +import * as ghActions from '@actions/core'; +import dedent from 'ts-dedent'; + +const isCI = process.env.GITHUB_ACTIONS === 'true'; + +class BeforeScriptExecutionError extends Error {} +class StorybookInitError extends Error {} const sbInit = async ( cwd: string, @@ -148,76 +155,150 @@ const runGenerators = async ( const limit = pLimit(1); - await Promise.all( + const generationResults = await Promise.allSettled( generators.map(({ dirName, name, script, expected, env }) => limit(async () => { - let flags: string[] = []; - if (expected.renderer === '@storybook/html') flags = ['--type html']; - else if (expected.renderer === '@storybook/server') flags = ['--type server']; - - const time = process.hrtime(); - console.log(`🧬 Generating ${name}`); - const baseDir = join(REPROS_DIRECTORY, dirName); const beforeDir = join(baseDir, BEFORE_DIR_NAME); - await emptyDir(baseDir); - - // We do the creation inside a temp dir to avoid yarn container problems - const createBaseDir = directory(); - if (!script.includes('pnp')) { - await setupYarn({ cwd: createBaseDir }); - } - - const createBeforeDir = join(createBaseDir, BEFORE_DIR_NAME); - - // Some tools refuse to run inside an existing directory and replace the contents, - // where as others are very picky about what directories can be called. So we need to - // handle different modes of operation. - if (script.includes('{{beforeDir}}')) { - const scriptWithBeforeDir = script.replaceAll('{{beforeDir}}', BEFORE_DIR_NAME); - await runCommand( - scriptWithBeforeDir, - { - cwd: createBaseDir, - timeout: SCRIPT_TIMEOUT, - }, - debug + try { + let flags: string[] = []; + if (expected.renderer === '@storybook/html') flags = ['--type html']; + else if (expected.renderer === '@storybook/server') flags = ['--type server']; + + const time = process.hrtime(); + console.log(`🧬 Generating ${name} (${{ dirName }})`); + await emptyDir(baseDir); + + // We do the creation inside a temp dir to avoid yarn container problems + const createBaseDir = directory(); + if (!script.includes('pnp')) { + await setupYarn({ cwd: createBaseDir }); + } + + const createBeforeDir = join(createBaseDir, BEFORE_DIR_NAME); + + // Some tools refuse to run inside an existing directory and replace the contents, + // where as others are very picky about what directories can be called. So we need to + // handle different modes of operation. + try { + if (script.includes('{{beforeDir}}')) { + const scriptWithBeforeDir = script.replaceAll('{{beforeDir}}', BEFORE_DIR_NAME); + await runCommand( + scriptWithBeforeDir, + { + cwd: createBaseDir, + timeout: SCRIPT_TIMEOUT, + }, + debug + ); + } else { + await ensureDir(createBeforeDir); + await runCommand(script, { cwd: createBeforeDir, timeout: SCRIPT_TIMEOUT }, debug); + } + } catch (error) { + const message = `❌ Failed to execute before-script for template: ${name} (${dirName})`; + if (isCI) { + ghActions.error(dedent`${message} + ${(error as any).stack}`); + } else { + console.error(message); + console.error(error); + } + throw new BeforeScriptExecutionError(message, { cause: error }); + } + + await localizeYarnConfigFiles(createBaseDir, createBeforeDir); + + // Now move the created before dir into it's final location and add storybook + await move(createBeforeDir, beforeDir); + + // Make sure there are no git projects in the folder + await remove(join(beforeDir, '.git')); + + try { + await addStorybook({ baseDir, localRegistry, flags, debug, env }); + } catch (error) { + const message = `❌ Failed to initialize Storybook in template: ${name} (${dirName})`; + if (isCI) { + ghActions.error(dedent`${message} + ${(error as any).stack}`); + } else { + console.error(message); + console.error(error); + } + throw new StorybookInitError(message, { + cause: error, + }); + } + await addDocumentation(baseDir, { name, dirName }); + + console.log( + `βœ… Generated ${name} (${dirName}) in ./${relative( + process.cwd(), + baseDir + )} successfully in ${prettyTime(process.hrtime(time))}` ); - } else { - await ensureDir(createBeforeDir); - await runCommand(script, { cwd: createBeforeDir, timeout: SCRIPT_TIMEOUT }, debug); + } catch (error) { + throw error; + } finally { + // Remove node_modules to save space and avoid GH actions failing + // They're not uploaded to the git sandboxes repo anyway + if (process.env.CLEANUP_SANDBOX_NODE_MODULES) { + console.log(`πŸ—‘οΈ Removing ${join(beforeDir, 'node_modules')}`); + await remove(join(beforeDir, 'node_modules')); + console.log(`πŸ—‘οΈ Removing ${join(baseDir, AFTER_DIR_NAME, 'node_modules')}`); + await remove(join(baseDir, AFTER_DIR_NAME, 'node_modules')); + } } + }) + ) + ); - await localizeYarnConfigFiles(createBaseDir, createBeforeDir); - - // Now move the created before dir into it's final location and add storybook - await move(createBeforeDir, beforeDir); + const hasGenerationErrors = generationResults.some((result) => result.status === 'rejected'); - // Make sure there are no git projects in the folder - await remove(join(beforeDir, '.git')); + if (!isCI) { + if (hasGenerationErrors) { + throw new Error(`Some sandboxes failed to generate`); + } + return; + } - await addStorybook({ baseDir, localRegistry, flags, debug, env }); + ghActions.summary.addHeading('Sandbox generation summary'); - await addDocumentation(baseDir, { name, dirName }); + if (!hasGenerationErrors) { + await ghActions.summary.addRaw('βœ… Success!').write(); + return; + } - // Remove node_modules to save space and avoid GH actions failing - // They're not uploaded to the git sandboxes repo anyway - if (process.env.CLEANUP_SANDBOX_NODE_MODULES) { - console.log(`πŸ—‘οΈ Removing ${join(beforeDir, 'node_modules')}`); - await remove(join(beforeDir, 'node_modules')); - console.log(`πŸ—‘οΈ Removing ${join(baseDir, AFTER_DIR_NAME, 'node_modules')}`); - await remove(join(baseDir, AFTER_DIR_NAME, 'node_modules')); + await ghActions.summary + .addRaw('Some sandboxes failed, see the job log for detailed errors') + .addTable([ + [ + { data: 'Name', header: true }, + { data: 'Key', header: true }, + { data: 'Result', header: true }, + ], + ...generationResults.map((result, index) => { + const { name, dirName } = generators[index]; + const row = [name, `\`${dirName}\``]; + if (result.status === 'fulfilled') { + row.push('🟒 Pass'); + return row; } + const generationError = (result as PromiseRejectedResult).reason as Error; + if (generationError instanceof BeforeScriptExecutionError) { + row.push('πŸ”΄ Failed to execute before script'); + } else if (generationError instanceof StorybookInitError) { + row.push('πŸ”΄ Failed to initialize Storybook'); + } else { + row.push('πŸ”΄ Failed with unknown error'); + } + return row; + }), + ]) + .write(); - console.log( - `βœ… Created ${dirName} in ./${relative( - process.cwd(), - baseDir - )} successfully in ${prettyTime(process.hrtime(time))}` - ); - }) - ) - ); + throw new Error(`Some sandboxes failed to generate`); }; export const options = createOptions({ @@ -278,7 +359,7 @@ if (esMain(import.meta.url)) { .action((optionValues) => { generate(optionValues) .catch((e) => { - console.trace(e); + console.error(e); process.exit(1); }) .then(() => { diff --git a/scripts/sandbox/publish.ts b/scripts/sandbox/publish.ts index e4307690efdd..334e8a9177eb 100755 --- a/scripts/sandbox/publish.ts +++ b/scripts/sandbox/publish.ts @@ -1,14 +1,15 @@ import program from 'commander'; -import { join } from 'path'; +import { dirname, join, relative } from 'path'; import { existsSync } from 'fs'; import * as tempy from 'tempy'; -import { copy, emptyDir, readdir, remove, stat, writeFile } from 'fs-extra'; +import { copy, emptyDir, remove, writeFile } from 'fs-extra'; import { execaCommand } from 'execa'; import { getTemplatesData, renderTemplate } from './utils/template'; // eslint-disable-next-line import/no-cycle import { commitAllToGit } from './utils/git'; import { REPROS_DIRECTORY } from '../utils/constants'; +import { glob } from 'glob'; export const logger = console; @@ -31,15 +32,21 @@ const publish = async (options: PublishOptions & { tmpFolder: string }) => { // otherwise old files will stick around and result inconsistent states logger.log(`πŸ—‘ Delete existing template dirs from clone`); - const files = await Promise.all( - ( - await readdir(REPROS_DIRECTORY) - ).map(async (f) => ({ path: f, stats: await stat(join(REPROS_DIRECTORY, f)) })) - ); + + // empty all existing directories for sandboxes that have a successful after-storybook directory await Promise.all( - files - .filter(({ stats, path }) => stats.isDirectory && !path.startsWith('.')) - .map(async ({ path }) => emptyDir(join(tmpFolder, path))) + // find all successfully generated after-storybook/README.md files + // eg. /home/repros/react-vite/default-ts/after-storybook/README.md + // README.md being the last file generated, thus representing a successful generation + (await glob(join(REPROS_DIRECTORY, '**', 'after-storybook/README.md'))).map((readmePath) => { + // get the after-storybook path relative to the source 'repros' directory + // eg. ./react-vite/default-ts/after-storybook + const pathRelativeToSource = relative(REPROS_DIRECTORY, dirname(readmePath)); + // get the actual path to the corresponding sandbox directory in the clone + // eg. /home/sandboxes-clone/react-vite/default-ts + const sandboxDirectoryToEmpty = join(tmpFolder, pathRelativeToSource, '..'); + return emptyDir(sandboxDirectoryToEmpty); + }) ); logger.log(`🚚 Moving template files into the repository`); diff --git a/scripts/yarn.lock b/scripts/yarn.lock index c87d27c84be7..4bd7c8b5c0c3 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1490,156 +1490,163 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-arm64@npm:0.18.20" +"@esbuild/aix-ppc64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/aix-ppc64@npm:0.20.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/android-arm64@npm:0.20.1" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-arm@npm:0.18.20" +"@esbuild/android-arm@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/android-arm@npm:0.20.1" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/android-x64@npm:0.18.20" +"@esbuild/android-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/android-x64@npm:0.20.1" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/darwin-arm64@npm:0.18.20" +"@esbuild/darwin-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/darwin-arm64@npm:0.20.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/darwin-x64@npm:0.18.20" +"@esbuild/darwin-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/darwin-x64@npm:0.20.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/freebsd-arm64@npm:0.18.20" +"@esbuild/freebsd-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/freebsd-arm64@npm:0.20.1" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/freebsd-x64@npm:0.18.20" +"@esbuild/freebsd-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/freebsd-x64@npm:0.20.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-arm64@npm:0.18.20" +"@esbuild/linux-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-arm64@npm:0.20.1" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-arm@npm:0.18.20" +"@esbuild/linux-arm@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-arm@npm:0.20.1" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-ia32@npm:0.18.20" +"@esbuild/linux-ia32@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-ia32@npm:0.20.1" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-loong64@npm:0.18.20" +"@esbuild/linux-loong64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-loong64@npm:0.20.1" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-mips64el@npm:0.18.20" +"@esbuild/linux-mips64el@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-mips64el@npm:0.20.1" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-ppc64@npm:0.18.20" +"@esbuild/linux-ppc64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-ppc64@npm:0.20.1" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-riscv64@npm:0.18.20" +"@esbuild/linux-riscv64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-riscv64@npm:0.20.1" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-s390x@npm:0.18.20" +"@esbuild/linux-s390x@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-s390x@npm:0.20.1" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/linux-x64@npm:0.18.20" +"@esbuild/linux-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/linux-x64@npm:0.20.1" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/netbsd-x64@npm:0.18.20" +"@esbuild/netbsd-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/netbsd-x64@npm:0.20.1" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/openbsd-x64@npm:0.18.20" +"@esbuild/openbsd-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/openbsd-x64@npm:0.20.1" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/sunos-x64@npm:0.18.20" +"@esbuild/sunos-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/sunos-x64@npm:0.20.1" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-arm64@npm:0.18.20" +"@esbuild/win32-arm64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/win32-arm64@npm:0.20.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-ia32@npm:0.18.20" +"@esbuild/win32-ia32@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/win32-ia32@npm:0.20.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.18.20": - version: 0.18.20 - resolution: "@esbuild/win32-x64@npm:0.18.20" +"@esbuild/win32-x64@npm:0.20.1": + version: 0.20.1 + resolution: "@esbuild/win32-x64@npm:0.20.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -2762,7 +2769,7 @@ __metadata: detect-port: "npm:^1.3.0" ejs: "npm:^3.1.8" ejs-lint: "npm:^2.0.0" - esbuild: "npm:^0.18.0" + esbuild: "npm:^0.20.1" esbuild-plugin-alias: "npm:^0.2.1" esbuild-register: "npm:^3.5.0" eslint: "npm:^8.56.0" @@ -6554,33 +6561,36 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.18.0": - version: 0.18.20 - resolution: "esbuild@npm:0.18.20" - dependencies: - "@esbuild/android-arm": "npm:0.18.20" - "@esbuild/android-arm64": "npm:0.18.20" - "@esbuild/android-x64": "npm:0.18.20" - "@esbuild/darwin-arm64": "npm:0.18.20" - "@esbuild/darwin-x64": "npm:0.18.20" - "@esbuild/freebsd-arm64": "npm:0.18.20" - "@esbuild/freebsd-x64": "npm:0.18.20" - "@esbuild/linux-arm": "npm:0.18.20" - "@esbuild/linux-arm64": "npm:0.18.20" - "@esbuild/linux-ia32": "npm:0.18.20" - "@esbuild/linux-loong64": "npm:0.18.20" - "@esbuild/linux-mips64el": "npm:0.18.20" - "@esbuild/linux-ppc64": "npm:0.18.20" - "@esbuild/linux-riscv64": "npm:0.18.20" - "@esbuild/linux-s390x": "npm:0.18.20" - "@esbuild/linux-x64": "npm:0.18.20" - "@esbuild/netbsd-x64": "npm:0.18.20" - "@esbuild/openbsd-x64": "npm:0.18.20" - "@esbuild/sunos-x64": "npm:0.18.20" - "@esbuild/win32-arm64": "npm:0.18.20" - "@esbuild/win32-ia32": "npm:0.18.20" - "@esbuild/win32-x64": "npm:0.18.20" +"esbuild@npm:^0.20.1": + version: 0.20.1 + resolution: "esbuild@npm:0.20.1" + dependencies: + "@esbuild/aix-ppc64": "npm:0.20.1" + "@esbuild/android-arm": "npm:0.20.1" + "@esbuild/android-arm64": "npm:0.20.1" + "@esbuild/android-x64": "npm:0.20.1" + "@esbuild/darwin-arm64": "npm:0.20.1" + "@esbuild/darwin-x64": "npm:0.20.1" + "@esbuild/freebsd-arm64": "npm:0.20.1" + "@esbuild/freebsd-x64": "npm:0.20.1" + "@esbuild/linux-arm": "npm:0.20.1" + "@esbuild/linux-arm64": "npm:0.20.1" + "@esbuild/linux-ia32": "npm:0.20.1" + "@esbuild/linux-loong64": "npm:0.20.1" + "@esbuild/linux-mips64el": "npm:0.20.1" + "@esbuild/linux-ppc64": "npm:0.20.1" + "@esbuild/linux-riscv64": "npm:0.20.1" + "@esbuild/linux-s390x": "npm:0.20.1" + "@esbuild/linux-x64": "npm:0.20.1" + "@esbuild/netbsd-x64": "npm:0.20.1" + "@esbuild/openbsd-x64": "npm:0.20.1" + "@esbuild/sunos-x64": "npm:0.20.1" + "@esbuild/win32-arm64": "npm:0.20.1" + "@esbuild/win32-ia32": "npm:0.20.1" + "@esbuild/win32-x64": "npm:0.20.1" dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true "@esbuild/android-arm": optional: true "@esbuild/android-arm64": @@ -6627,7 +6637,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 473b1d92842f50a303cf948a11ebd5f69581cd254d599dd9d62f9989858e0533f64e83b723b5e1398a5b488c0f5fd088795b4235f65ecaf4f007d4b79f04bc88 + checksum: 7e0303cb80defd55f3f7b85108081afc9c2f3852dda13bf70975a89210f20cd658fc02540d34247401806cb069c4ec489f7cf0df833e040ee361826484926c3a languageName: node linkType: hard @@ -14553,14 +14563,7 @@ __metadata: languageName: node linkType: hard -"tiny-invariant@npm:^1.3.1": - version: 1.3.1 - resolution: "tiny-invariant@npm:1.3.1" - checksum: 5b87c1d52847d9452b60d0dcb77011b459044e0361ca8253bfe7b43d6288106e12af926adb709a6fc28900e3864349b91dad9a4ac93c39aa15f360b26c2ff4db - languageName: node - linkType: hard - -"tiny-invariant@npm:^1.3.3": +"tiny-invariant@npm:^1.3.1, tiny-invariant@npm:^1.3.3": version: 1.3.3 resolution: "tiny-invariant@npm:1.3.3" checksum: 65af4a07324b591a059b35269cd696aba21bef2107f29b9f5894d83cc143159a204b299553435b03874ebb5b94d019afa8b8eff241c8a4cfee95872c2e1c1c4a