From 9e26d487449914a6b675959208c76078d6b23e43 Mon Sep 17 00:00:00 2001 From: Julien Huang Date: Fri, 20 Mar 2026 11:16:44 +0100 Subject: [PATCH 01/23] Cleanup: Move oxfmt config to root and reenable for all repo --- .agents/skills/pr/SKILL.md | 7 + .circleci/config.yml | 2 +- .github/DISCUSSION_TEMPLATE/help.yml | 74 +-- .github/ISSUE_TEMPLATE/tracking_issue.yml | 11 +- .github/PULL_REQUEST_TEMPLATE.md | 1 - .github/workflows/code-simplifier.lock.yml | 171 +++-- .github/workflows/cron-weekly.yml | 8 +- .github/workflows/danger-js.yml | 2 +- .../duplicate-code-detector.lock.yml | 149 +++-- .github/workflows/fork-checks.yml | 4 +- .github/workflows/handle-release-branches.yml | 3 +- .github/workflows/nx.yml | 16 +- .github/workflows/publish.yml | 6 +- .github/workflows/shared/mood.md | 2 +- .github/workflows/shared/reporting.md | 8 + .github/workflows/stale.yml | 10 +- .github/workflows/triage.yml | 10 +- .nx/workflows/agents.yaml | 2 +- .oxfmtrc.json | 36 +- .serena/memories/project_overview.md | 5 + .serena/memories/style_and_conventions.md | 6 + .serena/memories/suggested_commands.md | 13 +- .serena/memories/task_completion_checklist.md | 15 +- .serena/project.yml | 4 +- .yarnrc.yml | 2 +- AGENTS.md | 2 +- CHANGELOG.prerelease.md | 1 - CHANGELOG.v6.md | 1 - CODE_OF_CONDUCT.md | 21 +- CONTRIBUTING.md | 96 +-- CONTRIBUTING.old.md | 4 +- CONTRIBUTING/RELEASING.md | 50 +- MIGRATION.md | 26 +- code/.oxfmtrc.json | 34 - code/addons/docs/docs/frameworks/ANGULAR.md | 4 +- .../docs/src/blocks/blocks/Unstyled.mdx | 21 +- .../docs/template/stories/docs2/Error.mdx | 2 +- .../template/stories/docs2/ResolvedReact.mdx | 13 +- .../docs/template/stories/docs2/Tags.mdx | 2 +- .../stories/docs2/UtfSymbolScroll.mdx | 28 +- code/builders/builder-vite/input/iframe.html | 42 +- .../src/components/components/Button/Docs.mdx | 33 +- .../complex/MetaOfImportOrder.mdx | 5 +- .../complex/TwoStoryReferences.mdx | 3 +- .../src/docs2/ComponentReference.mdx | 6 +- .../utils/__mockdata__/src/docs2/Tags.mdx | 2 +- .../src/manager/components/sidebar/Menu.tsx | 14 +- .../nextjs-vite/template/cli/js/Configure.mdx | 79 +-- .../nextjs-vite/template/cli/ts/Configure.mdx | 79 +-- .../nextjs/template/cli/js/Configure.mdx | 79 +-- .../nextjs/template/cli/ts/Configure.mdx | 79 +-- .../rendererAssets/common/Configure.mdx | 59 +- code/package.json | 5 +- .../renderers/react/template/stories/csf4.mdx | 6 +- dependabot.yml | 8 +- docs/.oxfmtrc.json | 4 - .../angular-project-compodoc-config.md | 22 +- ...ividual-snapshot-tests-portable-stories.md | 18 +- .../login-form-with-play-function.md | 72 +-- docs/_snippets/mount-advanced.md | 24 +- ...rtable-stories-jest-multi-snapshot-test.md | 2 +- .../portable-stories-jest-override-globals.md | 4 +- .../portable-stories-jest-snapshot-test.md | 12 +- ...-stories-playwright-ct-override-globals.md | 4 +- ...able-stories-vitest-multi-snapshot-test.md | 2 +- ...ortable-stories-vitest-override-globals.md | 6 +- .../portable-stories-vitest-snapshot-test.md | 12 +- .../snapshot-tests-portable-stories.md | 24 +- .../storybook-addons-preset-viteFinal.md | 4 +- .../storybook-preview-use-global-type.md | 4 +- ...review-with-styled-components-decorator.md | 4 +- docs/_snippets/subpath-imports-config.md | 12 +- .../test-runner-custom-page-viewport.md | 4 +- docs/_snippets/vitest-plugin-vitest-config.md | 16 +- docs/addons/addon-knowledge-base.mdx | 56 +- docs/addons/addon-migration-guide.mdx | 26 +- docs/addons/addon-types.mdx | 18 +- docs/addons/addons-api.mdx | 72 ++- docs/addons/configure-addons.mdx | 2 +- docs/addons/index.mdx | 8 +- docs/addons/install-addons.mdx | 4 +- docs/addons/integration-catalog.mdx | 58 +- docs/addons/writing-addons.mdx | 17 +- docs/addons/writing-presets.mdx | 14 +- docs/ai/best-practices.mdx | 16 +- docs/ai/index.mdx | 8 +- docs/ai/manifests.mdx | 34 +- docs/ai/mcp/api.mdx | 10 +- docs/ai/mcp/overview.mdx | 6 +- docs/ai/mcp/sharing.mdx | 14 +- docs/api/arg-types.mdx | 129 ++-- docs/api/cli-options.mdx | 104 ++-- docs/api/csf/csf-next.mdx | 52 +- docs/api/csf/index.mdx | 60 +- docs/api/doc-blocks/doc-block-argtypes.mdx | 33 +- docs/api/doc-blocks/doc-block-canvas.mdx | 43 +- .../api/doc-blocks/doc-block-colorpalette.mdx | 3 +- docs/api/doc-blocks/doc-block-controls.mdx | 37 +- docs/api/doc-blocks/doc-block-icongallery.mdx | 3 + docs/api/doc-blocks/doc-block-meta.mdx | 14 +- docs/api/doc-blocks/doc-block-source.mdx | 45 +- docs/api/doc-blocks/doc-block-stories.mdx | 4 +- docs/api/doc-blocks/doc-block-story.mdx | 36 +- .../doc-blocks/doc-block-tableofcontents.mdx | 2 +- docs/api/doc-blocks/doc-block-unstyled.mdx | 30 +- docs/api/doc-blocks/doc-block-useof.mdx | 6 +- docs/api/doc-blocks/index.mdx | 2 +- docs/api/index.mdx | 18 +- docs/api/main-config/index.mdx | 2 +- .../main-config/main-config-babel-default.mdx | 4 +- docs/api/main-config/main-config-babel.mdx | 6 +- docs/api/main-config/main-config-build.mdx | 2 +- docs/api/main-config/main-config-core.mdx | 11 +- docs/api/main-config/main-config-features.mdx | 4 +- docs/api/main-config/main-config-indexers.mdx | 362 ++++++----- .../main-config/main-config-manager-head.mdx | 4 +- .../main-config-preview-annotations.mdx | 4 +- .../main-config/main-config-preview-body.mdx | 4 +- .../main-config/main-config-preview-head.mdx | 4 +- .../main-config/main-config-static-dirs.mdx | 4 +- docs/api/main-config/main-config-stories.mdx | 10 +- .../main-config/main-config-typescript.mdx | 68 +- docs/api/main-config/main-config.mdx | 47 +- docs/api/parameters.mdx | 50 +- docs/api/portable-stories/index.mdx | 2 +- .../portable-stories-jest.mdx | 258 ++++---- .../portable-stories-playwright.mdx | 141 +++-- .../portable-stories-vitest.mdx | 264 ++++---- docs/builders/builder-api.mdx | 6 +- docs/builders/index.mdx | 6 +- docs/builders/vite.mdx | 16 +- docs/builders/webpack.mdx | 18 +- docs/configure/environment-variables.mdx | 28 +- docs/configure/index.mdx | 22 +- docs/configure/integration/compilers.mdx | 28 +- .../frameworks-feature-support.mdx | 188 +++--- docs/configure/integration/frameworks.mdx | 26 +- .../integration/images-and-assets.mdx | 2 + docs/configure/integration/index.mdx | 2 +- docs/configure/integration/typescript.mdx | 72 ++- docs/configure/story-layout.mdx | 6 +- docs/configure/story-rendering.mdx | 21 +- docs/configure/styling-and-css.mdx | 280 +++++---- docs/configure/telemetry.mdx | 44 +- .../user-interface/features-and-behavior.mdx | 90 +-- docs/configure/user-interface/index.mdx | 2 +- .../user-interface/storybook-addons.mdx | 4 +- docs/configure/user-interface/theming.mdx | 28 +- docs/configure/webpack.mdx | 12 +- docs/contribute/RFC.mdx | 22 +- docs/contribute/code.mdx | 55 +- .../documentation/documentation-updates.mdx | 10 +- docs/contribute/documentation/index.mdx | 2 +- .../contribute/documentation/new-snippets.mdx | 24 +- docs/contribute/framework.mdx | 160 ++--- docs/contribute/how-to-reproduce.mdx | 28 +- docs/contribute/index.mdx | 18 +- docs/essentials/actions.mdx | 7 +- docs/essentials/backgrounds.mdx | 4 +- docs/essentials/controls.mdx | 171 ++--- docs/essentials/highlight.mdx | 38 +- docs/essentials/index.mdx | 18 +- docs/essentials/measure-and-outline.mdx | 4 +- docs/essentials/toolbars-and-globals.mdx | 44 +- docs/essentials/viewport.mdx | 70 +-- docs/faq.mdx | 217 +++---- docs/get-started/browse-stories.mdx | 32 +- docs/get-started/conclusion.mdx | 6 +- docs/get-started/frameworks/angular.mdx | 101 ++- docs/get-started/frameworks/index.mdx | 3 +- docs/get-started/frameworks/nextjs-vite.mdx | 79 +-- docs/get-started/frameworks/nextjs.mdx | 93 +-- docs/get-started/frameworks/preact-vite.mdx | 25 +- .../frameworks/react-native-web-vite.mdx | 64 +- docs/get-started/frameworks/react-vite.mdx | 24 +- .../get-started/frameworks/react-webpack5.mdx | 28 +- docs/get-started/frameworks/svelte-vite.mdx | 48 +- docs/get-started/frameworks/sveltekit.mdx | 79 +-- docs/get-started/frameworks/vue3-vite.mdx | 39 +- .../frameworks/web-components-vite.mdx | 15 +- docs/get-started/index.mdx | 2 +- docs/get-started/install.mdx | 183 +++--- docs/get-started/setup.mdx | 44 +- docs/get-started/whats-a-story.mdx | 24 +- docs/get-started/why-storybook.mdx | 27 +- docs/releases/features.mdx | 8 +- docs/releases/index.mdx | 7 +- .../migration-guide-from-older-version.mdx | 92 +-- docs/releases/migration-guide.mdx | 22 +- docs/releases/upgrading.mdx | 82 +-- docs/sharing/design-integrations.mdx | 18 +- docs/sharing/embed.mdx | 32 +- docs/sharing/index.mdx | 10 +- docs/sharing/package-composition.mdx | 4 +- docs/sharing/publish-storybook.mdx | 43 +- docs/sharing/storybook-composition.mdx | 20 +- docs/versions/latest.json | 2 +- docs/versions/next.json | 7 +- docs/writing-docs/autodocs.mdx | 43 +- docs/writing-docs/build-documentation.mdx | 20 +- docs/writing-docs/doc-blocks.mdx | 39 +- docs/writing-docs/mdx.mdx | 40 +- docs/writing-stories/args.mdx | 44 +- .../build-pages-with-storybook.mdx | 92 +-- docs/writing-stories/decorators.mdx | 64 +- docs/writing-stories/index.mdx | 81 +-- docs/writing-stories/loaders.mdx | 12 +- .../mocking-data-and-modules/index.mdx | 2 +- .../mocking-modules.mdx | 41 +- .../mocking-network-requests.mdx | 14 +- .../mocking-providers.mdx | 45 +- .../naming-components-and-hierarchy.mdx | 28 +- docs/writing-stories/parameters.mdx | 16 +- docs/writing-stories/play-function.mdx | 4 +- .../stories-for-multiple-components.mdx | 34 +- docs/writing-stories/typescript.mdx | 56 +- docs/writing-tests/accessibility-testing.mdx | 46 +- docs/writing-tests/in-ci.mdx | 71 ++- docs/writing-tests/index.mdx | 35 +- docs/writing-tests/integrations/index.mdx | 2 +- .../stories-in-end-to-end-tests.mdx | 26 +- .../integrations/stories-in-unit-tests.mdx | 52 +- .../integrations/test-runner.mdx | 146 +++-- .../integrations/vitest-addon/index.mdx | 63 +- .../vitest-addon/migration-guide.mdx | 7 +- docs/writing-tests/interaction-testing.mdx | 33 +- docs/writing-tests/snapshot-testing.mdx | 30 +- docs/writing-tests/test-coverage.mdx | 22 +- docs/writing-tests/visual-testing.mdx | 20 +- package.json | 2 +- scripts/ci/common-jobs.ts | 8 +- scripts/package.json | 4 +- scripts/project.json | 2 +- test-storybooks/ember-cli/.storybook/main.js | 6 +- test-storybooks/ember-cli/app/index.html | 2 +- .../ember-cli/app/templates/application.hbs | 2 +- .../app/templates/components/named-block.hbs | 6 +- .../templates/components/welcome-banner.hbs | 8 +- .../app/templates/components/welcome-page.hbs | 37 +- .../stories/welcome-banner.stories.js | 2 +- test-storybooks/external-docs/.babelrc | 2 +- .../external-docs/components/Template.mdx | 8 +- test-storybooks/external-docs/pages/index.mdx | 8 +- .../external-docs/styles/Home.module.css | 15 +- .../external-docs/styles/globals.css | 14 +- .../nextjs/.storybook/main.ts | 6 +- .../nextjs/jest.config.js | 10 +- .../nextjs/jest.setup.ts | 6 +- .../nextjs/pages/_app.tsx | 4 +- .../nextjs/pages/_document.tsx | 2 +- .../nextjs/pages/api/hello.ts | 9 +- .../nextjs/pages/index.tsx | 29 +- .../nextjs/stories/Image.stories.tsx | 2 +- .../nextjs/stories/NextHeader.tsx | 2 +- .../nextjs/styles/Home.module.css | 12 +- .../nextjs/styles/globals.css | 17 +- .../nextjs/typings.d.ts | 6 +- .../react-vitest-3/.eslintrc.cjs | 14 +- .../.storybook/get-decorator-string.ts | 2 +- .../react-vitest-3/.storybook/main.ts | 28 +- .../.storybook/setup-file-dependency.ts | 2 +- .../e2e-tests/component-testing.spec.ts | 583 +++++++---------- .../e2e-tests/save-from-controls.spec.ts | 58 +- .../react-vitest-3/playwright-e2e.config.ts | 31 +- .../react-vitest-3/pre-e2e.js | 8 +- .../stories/AddonTest.stories.tsx | 35 +- .../react-vitest-3/stories/Button.stories.tsx | 4 +- .../react-vitest-3/stories/Button.tsx | 2 +- .../stories/OtherComponent.stories.tsx | 5 +- .../stories/get-button-string.ts | 2 +- .../react-vitest-3/vite.config.ts | 4 +- .../react-vitest-3/vitest.workspace.ts | 20 +- .../react/.eslintrc.cjs | 14 +- .../react/.storybook/get-decorator-string.ts | 2 +- .../react/.storybook/main.ts | 28 +- .../react/.storybook/setup-file-dependency.ts | 2 +- .../react/cypress.config.ts | 6 +- .../react/cypress/support/commands.ts | 2 +- .../cypress/support/component-index.html | 10 +- .../react/cypress/support/component.ts | 10 +- .../react/e2e-tests/component-testing.spec.ts | 586 +++++++----------- .../e2e-tests/save-from-controls.spec.ts | 58 +- .../react/playwright-e2e.config.ts | 24 +- .../react/playwright/index.html | 24 +- .../react/playwright/index.ts | 4 +- .../react/pre-e2e.js | 8 +- .../react/stories/AddonTest.stories.tsx | 35 +- .../react/stories/Button.playwright.tsx | 16 +- .../stories/Button.stories.playwright.ts | 17 +- .../react/stories/Button.stories.tsx | 4 +- .../react/stories/Button.tsx | 2 +- .../react/stories/OtherComponent.stories.tsx | 5 +- .../react/stories/get-button-string.ts | 2 +- .../react/vite.config.mts | 20 +- .../svelte/.storybook/main.ts | 2 +- .../svelte/.storybook/preview.ts | 10 +- .../svelte/cypress.config.ts | 6 +- .../svelte/cypress/support/commands.ts | 2 +- .../cypress/support/component-index.html | 10 +- .../svelte/cypress/support/component.ts | 12 +- .../svelte/playwright.config.ts | 2 +- .../svelte/playwright/index.html | 2 +- .../svelte/playwright/index.ts | 8 +- .../svelte/stories/Button.cy.tsx | 6 +- .../svelte/stories/Button.playwright.ts | 2 +- .../stories/Button.stories.playwright.tsx | 2 +- .../svelte/svelte.config.js | 4 +- .../svelte/vite.config.ts | 12 +- .../vue3/.eslintrc.cjs | 14 +- .../vue3/.storybook/main.ts | 6 +- .../vue3/.storybook/preview.ts | 6 +- .../vue3/cypress.config.ts | 6 +- .../vue3/cypress/support/commands.ts | 2 +- .../vue3/cypress/support/component-index.html | 10 +- .../vue3/cypress/support/component.ts | 13 +- .../vue3/playwright.config.ts | 2 +- .../vue3/playwright/index.html | 24 +- .../vue3/playwright/index.ts | 8 +- .../vue3/stories/Button.cy.tsx | 30 +- .../vue3/stories/Button.playwright.tsx | 4 +- .../vue3/stories/Button.stories.portable.ts | 4 +- .../vue3/stories/Button.stories.ts | 3 +- .../vue3/vite.config.ts | 6 +- .../server-kitchen-sink/.storybook/main.ts | 6 +- .../standalone-preview/storybook.html | 2 +- test-storybooks/yarn-pnp/.storybook/main.ts | 14 +- .../yarn-pnp/.storybook/preview.ts | 12 +- test-storybooks/yarn-pnp/eslint.config.js | 36 +- test-storybooks/yarn-pnp/index.html | 24 +- test-storybooks/yarn-pnp/package.json | 2 +- test-storybooks/yarn-pnp/src/App.vue | 2 +- .../yarn-pnp/src/components/HelloWorld.vue | 15 +- test-storybooks/yarn-pnp/src/main.ts | 8 +- .../yarn-pnp/src/stories/Configure.mdx | 59 +- test-storybooks/yarn-pnp/tsconfig.json | 5 +- test-storybooks/yarn-pnp/vite.config.ts | 24 +- 336 files changed, 5405 insertions(+), 5070 deletions(-) delete mode 100644 code/.oxfmtrc.json delete mode 100644 docs/.oxfmtrc.json diff --git a/.agents/skills/pr/SKILL.md b/.agents/skills/pr/SKILL.md index ba97bec26cd9..a34c4512c4d1 100644 --- a/.agents/skills/pr/SKILL.md +++ b/.agents/skills/pr/SKILL.md @@ -47,6 +47,13 @@ Read `.github/PULL_REQUEST_TEMPLATE.md` from the repository root. Copy that template **EXACTLY**, including all HTML comments (``). Fill in the relevant sections based on the changes, but keep all comments intact. +## Validation reminders + +- Formatting is oxfmt-based in this repo. Use `yarn fmt:check` entrypoints, not Prettier. +- Typical checks before opening a PR: + - `cd code && yarn fmt:check` + - `cd scripts && yarn fmt:check` + ## Command Always create PRs in draft mode: diff --git a/.circleci/config.yml b/.circleci/config.yml index 59dd245f7676..e8f4e3547611 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,7 +30,7 @@ parameters: jobs: generate-and-run-config: - executor: + executor: name: node/default resource_class: small steps: diff --git a/.github/DISCUSSION_TEMPLATE/help.yml b/.github/DISCUSSION_TEMPLATE/help.yml index 2b74e6d0dcf8..738c0ec522d3 100644 --- a/.github/DISCUSSION_TEMPLATE/help.yml +++ b/.github/DISCUSSION_TEMPLATE/help.yml @@ -1,40 +1,40 @@ body: -- type: markdown - id: intro - attributes: - value: | - Thanks for taking the time to start a new discussion! - - ### Before you post - Check if someone has already asked/answered your question in a previous discussion. - - ### When you're ready to post - Add labels to your discussion (e.g. React, Vue, Vite) to make it clearer for other users. + - type: markdown + id: intro + attributes: + value: | + Thanks for taking the time to start a new discussion! -- type: textarea - id: summary - attributes: - label: Summary - description: How can we help? - validations: - required: true - -- type: textarea - id: additional-info - attributes: - label: Additional information - description: | - Share Your Storybook configuration (`main.js` or `main.ts`), your Storybook version number, any error messages, and any relevant dependencies. These help us get a clearer understanding of what might be going wrong. + ### Before you post + Check if someone has already asked/answered your question in a previous discussion. - P.S. Please [share code as text](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks) rather than as a screenshot! It makes debugging much easier and faster. - validations: - required: false - -- type: input - id: reproduction - attributes: - label: Create a reproduction - description: | - Help us debug by creating a minimal reproduction with [https://storybook.new](https://storybook.new). Learn more about creating a reproduction [here](https://storybook.js.org/docs/react/contribute/how-to-reproduce). - validations: - required: false + ### When you're ready to post + Add labels to your discussion (e.g. React, Vue, Vite) to make it clearer for other users. + + - type: textarea + id: summary + attributes: + label: Summary + description: How can we help? + validations: + required: true + + - type: textarea + id: additional-info + attributes: + label: Additional information + description: | + Share Your Storybook configuration (`main.js` or `main.ts`), your Storybook version number, any error messages, and any relevant dependencies. These help us get a clearer understanding of what might be going wrong. + + P.S. Please [share code as text](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks) rather than as a screenshot! It makes debugging much easier and faster. + validations: + required: false + + - type: input + id: reproduction + attributes: + label: Create a reproduction + description: | + Help us debug by creating a minimal reproduction with [https://storybook.new](https://storybook.new). Learn more about creating a reproduction [here](https://storybook.js.org/docs/react/contribute/how-to-reproduce). + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/tracking_issue.yml b/.github/ISSUE_TEMPLATE/tracking_issue.yml index 5031b85e15c1..a4df4133ff10 100644 --- a/.github/ISSUE_TEMPLATE/tracking_issue.yml +++ b/.github/ISSUE_TEMPLATE/tracking_issue.yml @@ -5,7 +5,7 @@ type: 'Tracking' body: - type: textarea attributes: - label: Problem statement + label: Problem statement description: A brief description of the problem we're trying to solve. validations: required: true @@ -25,23 +25,22 @@ body: **Complete by: `@date`** - [ ] task1 - [ ] task2 - + ### M2: milestone 2 **Owner: `@mention`** **Complete by: `@date`** - [ ] task1 - [ ] task2 - + ### Milestone n: be sure to always scope these in **Owner: `@mention`** **Complete by: `@date`** - [ ] Metric collection - [ ] Documentation - [ ] Blog posts - + **========= Everything below this line is strictly nice-to-have =========** - + ### Related issues that may be resolved by this project - [ ] issue1 - [ ] issue2 - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a57d93ef584e..5213e006305e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -52,7 +52,6 @@ Thank you for contributing to Storybook! Please submit all PRs to the `next` bra - [ ] Make sure this PR contains **one** of the labels below:
Available labels - - `bug`: Internal changes that fixes incorrect behavior. - `maintenance`: User-facing maintenance tasks. - `dependencies`: Upgrading (sometimes downgrading) dependencies. diff --git a/.github/workflows/code-simplifier.lock.yml b/.github/workflows/code-simplifier.lock.yml index f13e0e0ef6af..c4655d82f248 100644 --- a/.github/workflows/code-simplifier.lock.yml +++ b/.github/workflows/code-simplifier.lock.yml @@ -1,12 +1,12 @@ # -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ # | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ +# | | | | (_| | __/ | | | |_| | (__ # \_| |_/\__, |\___|_| |_|\__|_|\___| # __/ | -# _ _ |___/ +# _ _ |___/ # | | | | / _| | # | | | | ___ _ __ _ __| |_| | _____ ____ # | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| @@ -32,20 +32,20 @@ # # gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"dbddcd7da0eefb6c24a3380b4e555d7aacd3ba78c14d79ebc131c33cb86f02ea","compiler_version":"v0.55.0","strict":true} -name: "Code Simplifier" -"on": +name: 'Code Simplifier' +'on': schedule: - - cron: "6 12 * * *" - # Friendly format: daily (scattered) + - cron: '6 12 * * *' + # Friendly format: daily (scattered) # skip-if-match: is:pr is:open in:title "[code-simplifier]" # Skip-if-match processed as search check in pre-activation job workflow_dispatch: permissions: {} concurrency: - group: "gh-aw-${{ github.workflow }}" + group: 'gh-aw-${{ github.workflow }}' -run-name: "Code Simplifier" +run-name: 'Code Simplifier' jobs: activation: @@ -55,8 +55,8 @@ jobs: permissions: contents: read outputs: - comment_id: "" - comment_repo: "" + comment_id: '' + comment_repo: '' model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} steps: @@ -67,22 +67,22 @@ jobs: - name: Generate agentic run info id: generate_aw_info env: - GH_AW_INFO_ENGINE_ID: "copilot" - GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_ENGINE_ID: 'copilot' + GH_AW_INFO_ENGINE_NAME: 'GitHub Copilot CLI' GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} - GH_AW_INFO_VERSION: "" - GH_AW_INFO_AGENT_VERSION: "latest" - GH_AW_INFO_CLI_VERSION: "v0.55.0" - GH_AW_INFO_WORKFLOW_NAME: "Code Simplifier" - GH_AW_INFO_EXPERIMENTAL: "false" - GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" - GH_AW_INFO_STAGED: "false" + GH_AW_INFO_VERSION: '' + GH_AW_INFO_AGENT_VERSION: 'latest' + GH_AW_INFO_CLI_VERSION: 'v0.55.0' + GH_AW_INFO_WORKFLOW_NAME: 'Code Simplifier' + GH_AW_INFO_EXPERIMENTAL: 'false' + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: 'true' + GH_AW_INFO_STAGED: 'false' GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' - GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.23.0" - GH_AW_INFO_AWMG_VERSION: "" - GH_AW_INFO_FIREWALL_TYPE: "squid" - GH_AW_COMPILED_STRICT: "true" + GH_AW_INFO_FIREWALL_ENABLED: 'true' + GH_AW_INFO_AWF_VERSION: 'v0.23.0' + GH_AW_INFO_AWMG_VERSION: '' + GH_AW_INFO_FIREWALL_TYPE: 'squid' + GH_AW_COMPILED_STRICT: 'true' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | @@ -105,7 +105,7 @@ jobs: - name: Check workflow file timestamps uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_WORKFLOW_FILE: "code-simplifier.lock.yml" + GH_AW_WORKFLOW_FILE: 'code-simplifier.lock.yml' with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -168,7 +168,7 @@ jobs: - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} - + GH_AW_PROMPT_EOF cat << 'GH_AW_PROMPT_EOF' @@ -212,9 +212,9 @@ jobs: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - + const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); - + // Call the substitution function return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, @@ -256,11 +256,11 @@ jobs: issues: read pull-requests: read concurrency: - group: "gh-aw-copilot-${{ github.workflow }}" + group: 'gh-aw-copilot-${{ github.workflow }}' env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - GH_AW_ASSETS_ALLOWED_EXTS: "" - GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_ALLOWED_EXTS: '' + GH_AW_ASSETS_BRANCH: '' GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl @@ -587,17 +587,17 @@ jobs: # Mask immediately to prevent timing vulnerabilities API_KEY=$(openssl rand -base64 45 | tr -d '/+=') echo "::add-mask::${API_KEY}" - + PORT=3001 - + # Set outputs for next steps { echo "safe_outputs_api_key=${API_KEY}" echo "safe_outputs_port=${PORT}" } >> "$GITHUB_OUTPUT" - + echo "Safe Outputs MCP server will run on port ${PORT}" - + - name: Start Safe Outputs MCP HTTP Server id: safe-outputs-start env: @@ -615,9 +615,9 @@ jobs: export GH_AW_SAFE_OUTPUTS_TOOLS_PATH export GH_AW_SAFE_OUTPUTS_CONFIG_PATH export GH_AW_MCP_LOG_DIR - + bash /opt/gh-aw/actions/start_safe_outputs_server.sh - + - name: Start MCP Gateway id: start-mcp-gateway env: @@ -629,7 +629,7 @@ jobs: run: | set -eo pipefail mkdir -p /tmp/gh-aw/mcp-config - + # Export gateway environment variables for MCP config and gateway script export MCP_GATEWAY_PORT="80" export MCP_GATEWAY_DOMAIN="host.docker.internal" @@ -640,10 +640,10 @@ jobs: mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" export DEBUG="*" - + export GH_AW_ENGINE="copilot" export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.8' - + mkdir -p /home/runner/.copilot cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh { @@ -731,7 +731,7 @@ jobs: # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them SESSION_STATE_DIR="$HOME/.copilot/session-state" LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" - + if [ -d "$SESSION_STATE_DIR" ]; then echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" mkdir -p "$LOGS_DIR" @@ -780,7 +780,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GH_AW_ALLOWED_DOMAINS: 'api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com' GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} with: @@ -889,8 +889,8 @@ jobs: if: always() && steps.detection_guard.outputs.run_detection == 'true' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - WORKFLOW_NAME: "Code Simplifier" - WORKFLOW_DESCRIPTION: "Analyzes recently modified code and creates pull requests with simplifications that improve clarity, consistency, and maintainability while preserving functionality" + WORKFLOW_NAME: 'Code Simplifier' + WORKFLOW_DESCRIPTION: 'Analyzes recently modified code and creates pull requests with simplifications that improve clarity, consistency, and maintainability while preserving functionality' HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} with: script: | @@ -983,7 +983,7 @@ jobs: issues: write pull-requests: write concurrency: - group: "gh-aw-conclusion-code-simplifier" + group: 'gh-aw-conclusion-code-simplifier' cancel-in-progress: false outputs: noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1012,11 +1012,11 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_NOOP_MAX: "1" - GH_AW_WORKFLOW_NAME: "Code Simplifier" - GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md" - GH_AW_TRACKER_ID: "code-simplifier" + GH_AW_NOOP_MAX: '1' + GH_AW_WORKFLOW_NAME: 'Code Simplifier' + GH_AW_WORKFLOW_SOURCE: 'github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619' + GH_AW_WORKFLOW_SOURCE_URL: '${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md' + GH_AW_TRACKER_ID: 'code-simplifier' with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1029,10 +1029,10 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Code Simplifier" - GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md" - GH_AW_TRACKER_ID: "code-simplifier" + GH_AW_WORKFLOW_NAME: 'Code Simplifier' + GH_AW_WORKFLOW_SOURCE: 'github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619' + GH_AW_WORKFLOW_SOURCE_URL: '${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md' + GH_AW_TRACKER_ID: 'code-simplifier' with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1045,20 +1045,20 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Code Simplifier" - GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md" - GH_AW_TRACKER_ID: "code-simplifier" + GH_AW_WORKFLOW_NAME: 'Code Simplifier' + GH_AW_WORKFLOW_SOURCE: 'github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619' + GH_AW_WORKFLOW_SOURCE_URL: '${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md' + GH_AW_TRACKER_ID: 'code-simplifier' GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_WORKFLOW_ID: "code-simplifier" + GH_AW_WORKFLOW_ID: 'code-simplifier' GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} - GH_AW_GROUP_REPORTS: "false" - GH_AW_TIMEOUT_MINUTES: "30" + GH_AW_GROUP_REPORTS: 'false' + GH_AW_TIMEOUT_MINUTES: '30' with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1071,14 +1071,14 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Code Simplifier" - GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md" - GH_AW_TRACKER_ID: "code-simplifier" + GH_AW_WORKFLOW_NAME: 'Code Simplifier' + GH_AW_WORKFLOW_SOURCE: 'github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619' + GH_AW_WORKFLOW_SOURCE_URL: '${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md' + GH_AW_TRACKER_ID: 'code-simplifier' GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" + GH_AW_NOOP_REPORT_AS_ISSUE: 'true' with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1091,10 +1091,10 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Code Simplifier" - GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md" - GH_AW_TRACKER_ID: "code-simplifier" + GH_AW_WORKFLOW_NAME: 'Code Simplifier' + GH_AW_WORKFLOW_SOURCE: 'github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619' + GH_AW_WORKFLOW_SOURCE_URL: '${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md' + GH_AW_TRACKER_ID: 'code-simplifier' GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1130,9 +1130,9 @@ jobs: id: check_skip_if_match uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_SKIP_QUERY: "is:pr is:open in:title \"[code-simplifier]\"" - GH_AW_WORKFLOW_NAME: "Code Simplifier" - GH_AW_SKIP_MAX_MATCHES: "1" + GH_AW_SKIP_QUERY: 'is:pr is:open in:title "[code-simplifier]"' + GH_AW_WORKFLOW_NAME: 'Code Simplifier' + GH_AW_SKIP_MAX_MATCHES: '1' with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -1152,13 +1152,13 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/code-simplifier" - GH_AW_ENGINE_ID: "copilot" - GH_AW_TRACKER_ID: "code-simplifier" - GH_AW_WORKFLOW_ID: "code-simplifier" - GH_AW_WORKFLOW_NAME: "Code Simplifier" - GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md" + GH_AW_CALLER_WORKFLOW_ID: '${{ github.repository }}/code-simplifier' + GH_AW_ENGINE_ID: 'copilot' + GH_AW_TRACKER_ID: 'code-simplifier' + GH_AW_WORKFLOW_ID: 'code-simplifier' + GH_AW_WORKFLOW_NAME: 'Code Simplifier' + GH_AW_WORKFLOW_SOURCE: 'github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619' + GH_AW_WORKFLOW_SOURCE_URL: '${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md' outputs: code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} @@ -1219,10 +1219,10 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GH_AW_ALLOWED_DOMAINS: 'api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com' GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"expires\":24,\"labels\":[\"refactoring\",\"code-quality\",\"automation\"],\"max\":1,\"max_patch_size\":1024,\"reviewers\":[\"copilot\"],\"title_prefix\":\"[code-simplifier] \"},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: '{"create_pull_request":{"expires":24,"labels":["refactoring","code-quality","automation"],"max":1,"max_patch_size":1024,"reviewers":["copilot"],"title_prefix":"[code-simplifier] "},"missing_data":{},"missing_tool":{}}' GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1238,4 +1238,3 @@ jobs: name: safe-output-items path: /tmp/safe-output-items.jsonl if-no-files-found: warn - diff --git a/.github/workflows/cron-weekly.yml b/.github/workflows/cron-weekly.yml index 26269d89f3ba..19d462aeda5c 100644 --- a/.github/workflows/cron-weekly.yml +++ b/.github/workflows/cron-weekly.yml @@ -2,7 +2,7 @@ name: Markdown Links Check # runs every monday at 9 am on: schedule: - - cron: "0 9 * * 1" + - cron: '0 9 * * 1' permissions: contents: read # to fetch repository files for markdown link checks @@ -17,10 +17,10 @@ jobs: # checks all markdown files from important folders including all subfolders with: # only show errors that occur instead of successful links + errors - use-quiet-mode: "yes" + use-quiet-mode: 'yes' # output full HTTP info for broken links - use-verbose-mode: "yes" - config-file: ".github/workflows/markdown-link-check-config.json" + use-verbose-mode: 'yes' + config-file: '.github/workflows/markdown-link-check-config.json' # Notify to Discord channel on failure - name: Send Discord Notification if: failure() # Only run this step if previous steps failed diff --git a/.github/workflows/danger-js.yml b/.github/workflows/danger-js.yml index b2f6c6decbe8..ad9b13264dc6 100644 --- a/.github/workflows/danger-js.yml +++ b/.github/workflows/danger-js.yml @@ -8,7 +8,7 @@ on: - unlabeled - edited branches: - - "**" + - '**' concurrency: group: ${{ github.workflow }}-${{ github.event.number }} diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index ddc15bcdc28b..fe72ef225088 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -1,12 +1,12 @@ # -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ # | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ +# | | | | (_| | __/ | | | |_| | (__ # \_| |_/\__, |\___|_| |_|\__|_|\___| # __/ | -# _ _ |___/ +# _ _ |___/ # | | | | / _| | # | | | | ___ _ __ _ __| |_| | _____ ____ # | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| @@ -31,19 +31,19 @@ # # gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"8f718997fab9f4077b50cf09e67d1c93cb5d11105def34e97ec4d56929ca4323","compiler_version":"v0.55.0","strict":true} -name: "Duplicate Code Detector" -"on": +name: 'Duplicate Code Detector' +'on': schedule: - - cron: "12 8 * * *" - # Friendly format: daily (scattered) + - cron: '12 8 * * *' + # Friendly format: daily (scattered) workflow_dispatch: permissions: {} concurrency: - group: "gh-aw-${{ github.workflow }}" + group: 'gh-aw-${{ github.workflow }}' -run-name: "Duplicate Code Detector" +run-name: 'Duplicate Code Detector' jobs: activation: @@ -51,8 +51,8 @@ jobs: permissions: contents: read outputs: - comment_id: "" - comment_repo: "" + comment_id: '' + comment_repo: '' model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} steps: @@ -63,22 +63,22 @@ jobs: - name: Generate agentic run info id: generate_aw_info env: - GH_AW_INFO_ENGINE_ID: "copilot" - GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_ENGINE_ID: 'copilot' + GH_AW_INFO_ENGINE_NAME: 'GitHub Copilot CLI' GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} - GH_AW_INFO_VERSION: "" - GH_AW_INFO_AGENT_VERSION: "latest" - GH_AW_INFO_CLI_VERSION: "v0.55.0" - GH_AW_INFO_WORKFLOW_NAME: "Duplicate Code Detector" - GH_AW_INFO_EXPERIMENTAL: "false" - GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" - GH_AW_INFO_STAGED: "false" + GH_AW_INFO_VERSION: '' + GH_AW_INFO_AGENT_VERSION: 'latest' + GH_AW_INFO_CLI_VERSION: 'v0.55.0' + GH_AW_INFO_WORKFLOW_NAME: 'Duplicate Code Detector' + GH_AW_INFO_EXPERIMENTAL: 'false' + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: 'true' + GH_AW_INFO_STAGED: 'false' GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' - GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.23.0" - GH_AW_INFO_AWMG_VERSION: "" - GH_AW_INFO_FIREWALL_TYPE: "squid" - GH_AW_COMPILED_STRICT: "true" + GH_AW_INFO_FIREWALL_ENABLED: 'true' + GH_AW_INFO_AWF_VERSION: 'v0.23.0' + GH_AW_INFO_AWMG_VERSION: '' + GH_AW_INFO_FIREWALL_TYPE: 'squid' + GH_AW_COMPILED_STRICT: 'true' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | @@ -101,7 +101,7 @@ jobs: - name: Check workflow file timestamps uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_WORKFLOW_FILE: "duplicate-code-detector.lock.yml" + GH_AW_WORKFLOW_FILE: 'duplicate-code-detector.lock.yml' with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -162,7 +162,7 @@ jobs: - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} - + GH_AW_PROMPT_EOF cat << 'GH_AW_PROMPT_EOF' @@ -205,9 +205,9 @@ jobs: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - + const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); - + // Call the substitution function return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, @@ -249,11 +249,11 @@ jobs: issues: read pull-requests: read concurrency: - group: "gh-aw-copilot-${{ github.workflow }}" + group: 'gh-aw-copilot-${{ github.workflow }}' env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - GH_AW_ASSETS_ALLOWED_EXTS: "" - GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_ALLOWED_EXTS: '' + GH_AW_ASSETS_BRANCH: '' GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl @@ -577,17 +577,17 @@ jobs: # Mask immediately to prevent timing vulnerabilities API_KEY=$(openssl rand -base64 45 | tr -d '/+=') echo "::add-mask::${API_KEY}" - + PORT=3001 - + # Set outputs for next steps { echo "safe_outputs_api_key=${API_KEY}" echo "safe_outputs_port=${PORT}" } >> "$GITHUB_OUTPUT" - + echo "Safe Outputs MCP server will run on port ${PORT}" - + - name: Start Safe Outputs MCP HTTP Server id: safe-outputs-start env: @@ -605,9 +605,9 @@ jobs: export GH_AW_SAFE_OUTPUTS_TOOLS_PATH export GH_AW_SAFE_OUTPUTS_CONFIG_PATH export GH_AW_MCP_LOG_DIR - + bash /opt/gh-aw/actions/start_safe_outputs_server.sh - + - name: Start MCP Gateway id: start-mcp-gateway env: @@ -619,7 +619,7 @@ jobs: run: | set -eo pipefail mkdir -p /tmp/gh-aw/mcp-config - + # Export gateway environment variables for MCP config and gateway script export MCP_GATEWAY_PORT="80" export MCP_GATEWAY_DOMAIN="host.docker.internal" @@ -630,10 +630,10 @@ jobs: mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" export DEBUG="*" - + export GH_AW_ENGINE="copilot" export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.8' - + mkdir -p /home/runner/.copilot cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh { @@ -729,7 +729,7 @@ jobs: # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them SESSION_STATE_DIR="$HOME/.copilot/session-state" LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" - + if [ -d "$SESSION_STATE_DIR" ]; then echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" mkdir -p "$LOGS_DIR" @@ -778,7 +778,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GH_AW_ALLOWED_DOMAINS: 'api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com' GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} with: @@ -886,8 +886,8 @@ jobs: if: always() && steps.detection_guard.outputs.run_detection == 'true' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - WORKFLOW_NAME: "Duplicate Code Detector" - WORKFLOW_DESCRIPTION: "Identifies duplicate code patterns across the codebase and suggests refactoring opportunities" + WORKFLOW_NAME: 'Duplicate Code Detector' + WORKFLOW_DESCRIPTION: 'Identifies duplicate code patterns across the codebase and suggests refactoring opportunities' HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} with: script: | @@ -979,7 +979,7 @@ jobs: contents: read issues: write concurrency: - group: "gh-aw-conclusion-duplicate-code-detector" + group: 'gh-aw-conclusion-duplicate-code-detector' cancel-in-progress: false outputs: noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1008,10 +1008,10 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_NOOP_MAX: "1" - GH_AW_WORKFLOW_NAME: "Duplicate Code Detector" - GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md" + GH_AW_NOOP_MAX: '1' + GH_AW_WORKFLOW_NAME: 'Duplicate Code Detector' + GH_AW_WORKFLOW_SOURCE: 'github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619' + GH_AW_WORKFLOW_SOURCE_URL: '${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md' with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1024,9 +1024,9 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Duplicate Code Detector" - GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md" + GH_AW_WORKFLOW_NAME: 'Duplicate Code Detector' + GH_AW_WORKFLOW_SOURCE: 'github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619' + GH_AW_WORKFLOW_SOURCE_URL: '${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md' with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1039,17 +1039,17 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Duplicate Code Detector" - GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md" + GH_AW_WORKFLOW_NAME: 'Duplicate Code Detector' + GH_AW_WORKFLOW_SOURCE: 'github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619' + GH_AW_WORKFLOW_SOURCE_URL: '${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md' GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_WORKFLOW_ID: "duplicate-code-detector" + GH_AW_WORKFLOW_ID: 'duplicate-code-detector' GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} - GH_AW_GROUP_REPORTS: "false" - GH_AW_TIMEOUT_MINUTES: "15" + GH_AW_GROUP_REPORTS: 'false' + GH_AW_TIMEOUT_MINUTES: '15' with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1062,13 +1062,13 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Duplicate Code Detector" - GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md" + GH_AW_WORKFLOW_NAME: 'Duplicate Code Detector' + GH_AW_WORKFLOW_SOURCE: 'github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619' + GH_AW_WORKFLOW_SOURCE_URL: '${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md' GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" + GH_AW_NOOP_REPORT_AS_ISSUE: 'true' with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1086,12 +1086,12 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/duplicate-code-detector" - GH_AW_ENGINE_ID: "copilot" - GH_AW_WORKFLOW_ID: "duplicate-code-detector" - GH_AW_WORKFLOW_NAME: "Duplicate Code Detector" - GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md" + GH_AW_CALLER_WORKFLOW_ID: '${{ github.repository }}/duplicate-code-detector' + GH_AW_ENGINE_ID: 'copilot' + GH_AW_WORKFLOW_ID: 'duplicate-code-detector' + GH_AW_WORKFLOW_NAME: 'Duplicate Code Detector' + GH_AW_WORKFLOW_SOURCE: 'github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619' + GH_AW_WORKFLOW_SOURCE_URL: '${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md' outputs: code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} @@ -1124,11 +1124,11 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GH_AW_ALLOWED_DOMAINS: 'api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com' GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"assignees\":[\"copilot\"],\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" - GH_AW_ASSIGN_COPILOT: "true" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: '{"create_issue":{"assignees":["copilot"],"max":1},"missing_data":{},"missing_tool":{}}' + GH_AW_ASSIGN_COPILOT: 'true' with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1155,4 +1155,3 @@ jobs: name: safe-output-items path: /tmp/safe-output-items.jsonl if-no-files-found: warn - diff --git a/.github/workflows/fork-checks.yml b/.github/workflows/fork-checks.yml index 7a7bc9b4dcfe..fc7231756bf3 100644 --- a/.github/workflows/fork-checks.yml +++ b/.github/workflows/fork-checks.yml @@ -40,7 +40,9 @@ jobs: install-code-deps: true - name: oxfmt - run: cd code && yarn lint:fmt + run: | + cd code && yarn fmt:check + cd ../scripts && yarn fmt:check test: strategy: diff --git a/.github/workflows/handle-release-branches.yml b/.github/workflows/handle-release-branches.yml index 021ed04934ff..7d994a176c27 100644 --- a/.github/workflows/handle-release-branches.yml +++ b/.github/workflows/handle-release-branches.yml @@ -89,8 +89,7 @@ jobs: request-create-frontpage-branch: if: ${{ always() && github.repository_owner == 'storybookjs' }} - needs: - [branch-checks, next-release-branch-check, create-next-release-branch] + needs: [branch-checks, next-release-branch-check, create-next-release-branch] runs-on: ubuntu-latest steps: - if: ${{ needs.branch-checks.outputs.is-actionable-branch == 'true' && needs.branch-checks.outputs.is-latest-branch == 'false' && needs.next-release-branch-check.outputs.check == 'false' }} diff --git a/.github/workflows/nx.yml b/.github/workflows/nx.yml index ff5c96043417..e3f359473350 100644 --- a/.github/workflows/nx.yml +++ b/.github/workflows/nx.yml @@ -103,24 +103,24 @@ jobs: const tag = ${{ toJson(steps.tag.outputs.tag) }} || ''; const lines = raw.split('\n'); const failures = []; - + for (const [i, line] of lines.entries()) { if (!line.includes('✖')) continue; - + const task = line.match(/✖\s+([^│]+?)\s{2,}/)?.[1].trim() || 'Unknown Nx task'; - + const url = lines .slice(i + 1, i + 6) .find(l => l.includes('Task logs:')) ?.match(/Task logs:\s*(https:\/\/cloud\.nx\.app\/logs\/\S+)/)?.[1]; - + failures.push({ task, url }); } - + const sha = context.payload.pull_request?.head?.sha ?? context.sha; - + // Per-task statuses (max 5) for (const { task, url } of failures.slice(0, 5)) { await github.rest.repos.createCommitStatus({ @@ -133,7 +133,7 @@ jobs: description: 'Your test failed on NX Cloud', }); } - + const runMatches = raw.match(/https:\/\/cloud\.nx\.app\/runs\/\S+/g); const nxCloudUrl = runMatches ? runMatches[runMatches.length - 1] : undefined; @@ -149,4 +149,4 @@ jobs: ? `Nx Cloud run failed (${failedCount} tasks failed)` : 'Nx Cloud run finished successfully', context: `nx: ${tag}`, - }); \ No newline at end of file + }); diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8259f525d13e..76888cb1ccfe 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,7 +11,7 @@ on: # Manual canary releases on PRs inputs: pr: - description: "⚠️ CANARY RELEASES ONLY - Enter the pull request number to create a canary release for" + description: '⚠️ CANARY RELEASES ONLY - Enter the pull request number to create a canary release for' required: true type: number pull_request: @@ -218,7 +218,7 @@ jobs: DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }} uses: Ilshidur/action-discord@d2594079a10f1d6739ee50a2471f0ca57418b554 with: - args: "The GitHub Action for publishing version ${{ steps.version.outputs.current-version }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + args: 'The GitHub Action for publishing version ${{ steps.version.outputs.current-version }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' publish-canary: name: Publish canary version @@ -288,7 +288,7 @@ jobs: with: githubToken: ${{ secrets.GH_TOKEN }} prNumber: ${{ github.event_name == 'workflow_dispatch' && inputs.pr || '' }} - find: "CANARY_RELEASE_SECTION" + find: 'CANARY_RELEASE_SECTION' isHtmlCommentTag: true replace: | This pull request has been released as version `${{ steps.version.outputs.next-version }}`. Try it out in a new sandbox by running `npx storybook@${{ steps.version.outputs.next-version }} sandbox` or in an existing project with `npx storybook@${{ steps.version.outputs.next-version }} upgrade`. diff --git a/.github/workflows/shared/mood.md b/.github/workflows/shared/mood.md index 945c9b46d684..9c558e357c41 100644 --- a/.github/workflows/shared/mood.md +++ b/.github/workflows/shared/mood.md @@ -1 +1 @@ -. \ No newline at end of file +. diff --git a/.github/workflows/shared/reporting.md b/.github/workflows/shared/reporting.md index bc08afb42be9..7b821ccff7b3 100644 --- a/.github/workflows/shared/reporting.md +++ b/.github/workflows/shared/reporting.md @@ -5,17 +5,21 @@ ## Report Structure Guidelines ### 1. Header Levels + **Use h3 (###) or lower for all headers in your issue report to maintain proper document hierarchy.** When creating GitHub issues or discussions: + - Use `###` (h3) for main sections (e.g., "### Test Summary") - Use `####` (h4) for subsections (e.g., "#### Device-Specific Results") - Never use `##` (h2) or `#` (h1) in reports - these are reserved for titles ### 2. Progressive Disclosure + **Wrap detailed test results in `
Section Name` tags to improve readability and reduce scrolling.** Use collapsible sections for: + - Verbose details (full test logs, raw data) - Secondary information (minor warnings, extra context) - Per-item breakdowns when there are many items @@ -32,6 +36,7 @@ Always keep critical information visible (summary, critical issues, key metrics) ### Design Principles (Airbnb-Inspired) Reports should: + - **Build trust through clarity**: Most important info immediately visible - **Exceed expectations**: Add helpful context like trends, comparisons - **Create delight**: Use progressive disclosure to reduce overwhelm @@ -41,11 +46,13 @@ Reports should: ```markdown ### Summary + - Key metric 1: value - Key metric 2: value - Status: ✅/⚠️/❌ ### Critical Issues + [Always visible - these are important]
@@ -63,6 +70,7 @@ Reports should:
### Recommendations + [Actionable next steps - keep visible] ``` diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 55a49c5f04d2..b1ad6c8ac858 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,7 +1,7 @@ -name: "Close stale issues that need reproduction or more info from OP" +name: 'Close stale issues that need reproduction or more info from OP' on: schedule: - - cron: "30 1 * * *" + - cron: '30 1 * * *' permissions: issues: write # to close and label issues (actions/stale) @@ -16,9 +16,9 @@ jobs: with: stale-issue-message: "Hi there! Thank you for opening this issue, but it has been marked as `stale` because we need more information to move forward. Could you please provide us with the requested reproduction or additional information that could help us better understand the problem? We'd love to resolve this issue, but we can't do it without your help!" close-issue-message: "I'm afraid we need to close this issue for now, since we can't take any action without the requested reproduction or additional information. But please don't hesitate to open a new issue if the problem persists – we're always happy to help. Thanks so much for your understanding." - any-of-issue-labels: "needs reproduction,needs more info" - exempt-issue-labels: "needs triage" - labels-to-add-when-unstale: "needs triage" + any-of-issue-labels: 'needs reproduction,needs more info' + exempt-issue-labels: 'needs triage' + labels-to-add-when-unstale: 'needs triage' days-before-issue-close: 7 days-before-issue-stale: 21 days-before-pr-close: -1 diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index af45b109da06..112c932fb829 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -24,8 +24,8 @@ jobs: { "good first issue": ".github/comments/good-first-issue.md" } - reproduction-comment: ".github/comments/invalid-link.md" - reproduction-hosts: "github.com,codesandbox.io,stackblitz.com" - reproduction-link-section: "### Reproduction link(.*)### Reproduction steps" - reproduction-invalid-label: "needs reproduction" - reproduction-issue-labels: "bug,needs triage" + reproduction-comment: '.github/comments/invalid-link.md' + reproduction-hosts: 'github.com,codesandbox.io,stackblitz.com' + reproduction-link-section: '### Reproduction link(.*)### Reproduction steps' + reproduction-invalid-label: 'needs reproduction' + reproduction-issue-labels: 'bug,needs triage' diff --git a/.nx/workflows/agents.yaml b/.nx/workflows/agents.yaml index 5cc496ca06c3..563eefdeca49 100644 --- a/.nx/workflows/agents.yaml +++ b/.nx/workflows/agents.yaml @@ -92,4 +92,4 @@ launch-templates: resource-class: 'windows/medium' image: 'windows-2022' # TODO init-steps: - env: *common-env-vars \ No newline at end of file + env: *common-env-vars diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 87673614cded..f8b549959902 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -6,21 +6,29 @@ "singleQuote": true, "arrowParens": "always", "sortPackageJson": false, + "embeddedLanguageFormatting": "off", "ignorePatterns": [ - "code", - "test-storybooks", - "node_modules", + "*.bundle.js", + "*.js.map", ".yarn", - ".nx", ".vscode", - ".github", - "*.md", - "*.mdx", - "*.yml", - "*.yaml", - "docs/versions", - "CHANGELOG*", - "MIGRATION*", - "CONTRIBUTING*" + ".nx/cache", + ".nx/workspace-data", + "dist", + "build", + "bench", + "coverage", + "node_modules", + "storybook-static", + "built-storybooks", + "ember-output", + "code/core/assets", + "code/core/report", + "code/core/src/core-server/presets/common-manager.ts", + "code/core/src/core-server/utils/__search-files-tests__", + "code/core/src/core-server/utils/__mockdata__/src/Empty.stories.ts", + "code/lib/codemod/src/transforms/__testfixtures__", + "code/frameworks/angular/template/**", + ".prettierrc" ] -} +} \ No newline at end of file diff --git a/.serena/memories/project_overview.md b/.serena/memories/project_overview.md index ecf7e7e15cde..aa66341cf527 100644 --- a/.serena/memories/project_overview.md +++ b/.serena/memories/project_overview.md @@ -1,14 +1,17 @@ # Storybook - Project Overview ## Purpose + Storybook is an open-source UI development tool for building, testing, and documenting UI components in isolation. It supports multiple frontend frameworks (React, Vue, Angular, Svelte, Web Components, Preact, Ember, HTML, etc.) and integrates with various build tools (Vite, Webpack5). ## Version + Current version: 10.2.x (as of March 2026) ## Tech Stack + - **Language**: TypeScript (strict mode), targeting ES2020 - **Package Manager**: Yarn 4.10.3 (with workspaces) - **Node.js**: 22.21.1 (specified in `.nvmrc`) @@ -21,6 +24,7 @@ Current version: 10.2.x (as of March 2026) - **Build System**: Custom build via `jiti ./scripts/build/build-package.ts` ## Repository Structure + ``` storybook/ ├── code/ # Main codebase @@ -40,6 +44,7 @@ storybook/ ``` ## Key Packages + - `@storybook/core` - Core functionality (UI, API, server, preview, channels, etc.) - `@storybook/react`, `@storybook/vue3`, etc. - Framework renderers - `@storybook/react-vite`, `@storybook/nextjs`, etc. - Framework integrations diff --git a/.serena/memories/style_and_conventions.md b/.serena/memories/style_and_conventions.md index 341ef193bb4b..682262e5ebb1 100644 --- a/.serena/memories/style_and_conventions.md +++ b/.serena/memories/style_and_conventions.md @@ -1,6 +1,7 @@ # Code Style & Conventions ## TypeScript + - **Strict mode** enabled (`strict: true` in tsconfig) - Target: ES2020, Module: Preserve, ModuleResolution: bundler - `noImplicitAny: true` @@ -8,6 +9,7 @@ - No emit (handled by build tools, not tsc) ## Prettier Configuration + - Print width: 100 - Tab width: 2 - Single quotes: yes @@ -27,6 +29,7 @@ - Import specifiers sorted: yes ## ESLint Rules (Notable) + - `react-aria` and `react-stately`: must import from specific submodules (e.g., `@react-aria/overlays`), NOT root - `react-aria-components`: must use `react-aria-components/patched-dist/ComponentX` entrypoints for tree-shaking - `es-toolkit`: must use sub-exports (e.g., `es-toolkit/array`), NOT root import @@ -36,6 +39,7 @@ - Custom local rules: `no-uncategorized-errors`, `storybook-monorepo-imports`, `no-duplicated-error-codes` ## Naming Conventions + - Files: kebab-case for most files (e.g., `my-component.ts`) - Components: PascalCase for React components - Types/Interfaces: PascalCase @@ -43,11 +47,13 @@ - Constants: UPPER_SNAKE_CASE for true constants, camelCase otherwise ## Test Files + - Pattern: `*.test.ts`, `*.test.tsx`, `*.spec.ts`, `*.spec.tsx` - Stories: `*.stories.ts`, `*.stories.tsx` - Test fixtures in `__testfixtures__/` directories - Tests in `__tests__/` directories or alongside source files ## Monorepo Import Rules + - Internal packages use `workspace:*` for dependencies - Custom ESLint rule `storybook-monorepo-imports` enforces correct import patterns within the monorepo diff --git a/.serena/memories/suggested_commands.md b/.serena/memories/suggested_commands.md index a63c1ab401d2..6d6b4623058b 100644 --- a/.serena/memories/suggested_commands.md +++ b/.serena/memories/suggested_commands.md @@ -1,11 +1,13 @@ # Suggested Commands ## Installation + ```bash yarn install # Install all dependencies (from repo root) ``` ## Building + ```bash # Compile a single package (always use --no-cloud to avoid NX Cloud issues) yarn nx compile core --no-cloud @@ -19,6 +21,7 @@ cd code && yarn build ``` ## Testing + ```bash # Run all unit tests (from repo root or code/) cd code && yarn test @@ -36,6 +39,7 @@ cd code && npx playwright test ``` ## Linting & Formatting + ```bash # Run all linting cd code && yarn lint @@ -43,14 +47,15 @@ cd code && yarn lint # Lint JS/TS only cd code && yarn lint:js -# Format with Prettier -cd code && yarn lint:prettier '**/*.{css,html,json,md,yml}' +# Format check with oxfmt +cd code && yarn fmt:check # Run knip (unused code detection) cd code && yarn knip ``` ## Running the Internal Dev Storybook + ```bash # Must compile core first! yarn nx compile core --no-cloud @@ -66,6 +71,7 @@ cd code && yarn storybook:ui:build ``` ## Sandbox / Task System + ```bash # Start a sandbox with a specific template yarn start # defaults to react-vite/default-ts @@ -75,6 +81,7 @@ yarn task --task dev --template react-vite/default-ts --start-from=install ``` ## NX Commands + ```bash # Always use --no-cloud flag! yarn nx compile --no-cloud @@ -83,6 +90,7 @@ yarn nx show projects --affected ``` ## Git + ```bash git status git diff @@ -91,6 +99,7 @@ git checkout next # main branch is "next" ``` ## System Utilities (macOS/Darwin) + ```bash ls, cd, pwd, cat, head, tail grep, find, xargs diff --git a/.serena/memories/task_completion_checklist.md b/.serena/memories/task_completion_checklist.md index 999e01e9da80..44f265913487 100644 --- a/.serena/memories/task_completion_checklist.md +++ b/.serena/memories/task_completion_checklist.md @@ -3,37 +3,48 @@ After completing a coding task, run through these steps: ## 1. TypeScript Compilation + Ensure the modified package(s) compile without errors: + ```bash yarn nx compile --no-cloud ``` ## 2. Linting + Run lint on the changed files or the whole codebase: + ```bash cd code && yarn lint:js ``` ## 3. Formatting + Ensure code is properly formatted: + ```bash -cd code && yarn lint:prettier '' +cd code && yarn fmt:check ``` ## 4. Unit Tests + Run relevant tests: + ```bash cd code && yarn test ``` ## 5. Pre-commit Checks + The project uses husky + lint-staged for pre-commit hooks: + - JS/TS files: ESLint with `--fix` - EJS files: ejslint -- CSS/HTML/JSON/MD/YML: Prettier +- CSS/HTML/JSON/MD/YML: oxfmt - package.json: lint:package ## Notes + - The main branch is `next` (not `main` or `master`) - Always use `--no-cloud` with NX commands - Before starting the dev server, ensure core is compiled: `yarn nx compile core --no-cloud` diff --git a/.serena/project.yml b/.serena/project.yml index 09c6279d5f4b..f2432129ce61 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -1,9 +1,9 @@ -project_name: "storybook" +project_name: 'storybook' languages: - typescript -encoding: "utf-8" +encoding: 'utf-8' ignore_all_files_in_gitignore: true ignored_paths: [] diff --git a/.yarnrc.yml b/.yarnrc.yml index 8751684b3867..880478bfdfa6 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -18,7 +18,7 @@ npmPublishAccess: public tsEnableAutoTypes: true -npmRegistryServer: "https://registry.yarnpkg.com" +npmRegistryServer: 'https://registry.yarnpkg.com' # See https://github.com/nrwl/nx/issues/22177 # This is a workaround for a yarn berry issue and remote caching in NX. diff --git a/AGENTS.md b/AGENTS.md index 6840a21614a8..945a7789e337 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -234,7 +234,7 @@ When writing tests: After changing files: -1. Format with `cd code && oxfmt` +1. Format/check with `cd code && yarn fmt:check` and `cd scripts && yarn fmt:check` when docs snippets are affected 2. Lint with `yarn --cwd code lint:js:cmd --fix` or `cd code && yarn lint:js:cmd ` 3. Run relevant tests before submitting a PR diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 4a9dd489a3d3..1c9b5a9986c7 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -11,7 +11,6 @@ ## 10.4.0-alpha.0 - ## 10.3.0-beta.3 - Addon-Vitest: Handle additional vitest config export patterns in postinstall - [#34106](https://github.com/storybookjs/storybook/pull/34106), thanks @copilot-swe-agent! diff --git a/CHANGELOG.v6.md b/CHANGELOG.v6.md index 5e7ce4179aee..95780799d0c7 100644 --- a/CHANGELOG.v6.md +++ b/CHANGELOG.v6.md @@ -4085,7 +4085,6 @@ NPM publish failed - Manager caching for faster startup [#12707](https://github.com/storybookjs/storybook/pull/12707) - Asynchronous loaders [#12699](https://github.com/storybookjs/storybook/pull/12699) - React improvements - - React 17 support [#12972](https://github.com/storybookjs/storybook/pull/12972) [#12975](https://github.com/storybookjs/storybook/pull/12975) - Fast refresh [#12470](https://github.com/storybookjs/storybook/pull/12470) [#12535](https://github.com/storybookjs/storybook/pull/12535) - Strict mode [#12781](https://github.com/storybookjs/storybook/pull/12781) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 63f02009935a..7a2c64a8e200 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,3 @@ - # Contributor Covenant Code of Conduct ## Our Pledge @@ -18,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or advances of +- The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2580150e27ab..2bd604a57bb1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,10 @@ # Contributors Guide -> Tip: If you want to make a fast contribution, check the “good first issue” label in the Issues tab for small frontend and docs tasks that are easy to fix directly on GitHub. +> Tip: If you want to make a fast contribution, check the “good first issue” label in the Issues tab for small frontend and docs tasks that are easy to fix directly on GitHub. We welcome contributions of any type and skill level. As an open-source project, we believe in the power of community and welcome any contributions that help us improve Storybook. Whether you are a developer, designer, writer, or someone who wants to help, we'd love to have you on board. If you are interested in contributing, please read the following guidelines. -Whether you're new to open source or a seasoned contributor, we welcome all contributions. Here are a few ways you can contribute to Storybook: +Whether you're new to open source or a seasoned contributor, we welcome all contributions. Here are a few ways you can contribute to Storybook: - [Create an RFC](https://storybook.js.org/docs/contribute/RFC) for feature requests - Update our [documentation](https://storybook.js.org/docs/contribute/documentation/documentation-updates) with fixes, improvements, or clarifications @@ -20,26 +20,26 @@ If you're not sure where to start, you can always help us by: > **Note**: Before you start contributing, please read the [Code of Conduct](./CODE_OF_CONDUCT.md) and reach out to the maintainers if you have any questions or concerns about the project or the contribution process on the [`#contributing`](https://discord.com/channels/486522875931656193/839297503446695956) channel on Discord. -## Quick guide +## Quick guide -### Prerequisites +### Prerequisites Storybook is developed against a specific Node.js version specified in the `.nvmrc` file. You can use any version manager to install the correct version of Node.js. We recommend using [fnm](https://github.com/Schniz/fnm). 1. Check if you have the correct version of Node.js installed by running the following command: - - ```shell - # Check which version you're using - node --version - # node version manager - nvm use 22 - # pnpm - pnpm env use --global 22 - ``` + +```shell +# Check which version you're using +node --version +# node version manager +nvm use 22 +# pnpm +pnpm env use --global 22 +``` 2. Install [fnm](https://github.com/Schniz/fnm/tree/master?tab=readme-ov-file#installation) and adjust your shell configuration to include the following parameters: `fnm env`, `use-on-cd`, `corepack-enabled`, and `version-file-strategy recursive`. - + ```shell eval "$(fnm env --use-on-cd --corepack-enabled --version-file-strategy recursive)" ``` @@ -128,7 +128,7 @@ Here's a highlight of notable directories and files: └── yarn.lock ``` -### Fork the repository +### Fork the repository If you plan to contribute to Storybook's codebase, you should fork the repository to your GitHub account. This will allow you to make changes to the codebase and submit a pull request to the main repository when you're ready to contribute your changes. @@ -140,53 +140,53 @@ git fetch upstream git branch --set-upstream-to upstream/main main ``` -### Running the local development environment +### Running the local development environment If you're interested in contributing to Storybook's codebase, you can run it locally to get a feel for the codebase and the development environment. To get started with the development environment, you should always run `yarn start` from the root directory. Running `yarn start` will install the required dependencies, build the project, including the packages, and generate a sandbox environment using React with TypeScript with a set of test stories to help you get started. ```shell -# Navigate to the root directory of the Storybook repository -cd path/to/your/storybook/fork +# Navigate to the root directory of the Storybook repository +cd path/to/your/storybook/fork # Install the required dependencies yarn -# start the development environment +# start the development environment yarn start ``` -### Making code changes +### Making code changes -If you want to make code changes to Storybook packages while running a sandbox, you'll need to do the following: +If you want to make code changes to Storybook packages while running a sandbox, you'll need to do the following: 1. In a second terminal, run `yarn build --watch ` in the `code/` directory. -For example, to build the `@storybook/react`, `storybook` itself, `@storybook/builder-vite`, and `@storybook/addon-docs` packages, you would run: +For example, to build the `@storybook/react`, `storybook` itself, `@storybook/builder-vite`, and `@storybook/addon-docs` packages, you would run: -```shell -# Navigate to the code directory -cd path/to/your/storybook/fork/code +````shell +# Navigate to the code directory +cd path/to/your/storybook/fork/code -# Build the specified packages in watch mode -yarn build --watch react core-server api addon-docs +# Build the specified packages in watch mode +yarn build --watch react core-server api addon-docs Most package names can be found after `@storybook/` in the published package. For instance, to build the `@storybook/react storybook @storybook/builder-vite @storybook/addon-docs` packages at the same time in watch mode: -```shell +```shell cd code -yarn build --watch react storybook builder-vite addon-docs -``` +yarn build --watch react storybook builder-vite addon-docs +```` -2. If you are running the sandbox in ["linked"](https://yarnpkg.com/cli/link) mode (the default), you should see the changes reflected on a refresh (you may need to restart it if changing server packages) +2. If you are running the sandbox in ["linked"](https://yarnpkg.com/cli/link) mode (the default), you should see the changes reflected on a refresh (you may need to restart it if changing server packages) -3. If you are running the sandbox in "unlinked" mode, you'll need to rerun the sandbox from the `publish` step to see the changes: +3. If you are running the sandbox in "unlinked" mode, you'll need to rerun the sandbox from the `publish` step to see the changes: -```shell -yarn task --task dev --template --start-from=publish -``` +```shell +yarn task --task dev --template --start-from=publish +``` -4. If you have made any changes inside `/code` or other packages, remember to run `yarn test` inside the package to ensure that your changes do not break any tests. +4. If you have made any changes inside `/code` or other packages, remember to run `yarn test` inside the package to ensure that your changes do not break any tests. ### Angular-specific code @@ -202,7 +202,7 @@ yarn task --prod yarn build --prod --watch angular storybook addon-docs ``` -### Running against different sandbox templates +### Running against different sandbox templates You can pick a specific template to use as your sandbox by running `yarn task`, which will prompt you to make further choices about which template you want and which task you want to run. @@ -217,26 +217,26 @@ https://github.com/storybookjs/storybook/blob/3d49093954243d4d520774243866de840f In fact you can filter on any job you wish, only running `test-runner`, `e2e`, `vite`-sandboxes, etc. -## Troubleshooting +## Troubleshooting -### The initialization process throws an error +### The initialization process throws an error -If you run `yarn start` and encounter the following error, try rerunning `yarn start` a second time: +If you run `yarn start` and encounter the following error, try rerunning `yarn start` a second time: -```shell -> NX ENOENT: no such file or directory, open 'storybook/code/node_modules/nx/package.json' +```shell +> NX ENOENT: no such file or directory, open 'storybook/code/node_modules/nx/package.json' ``` -### Storybook doesn't detect changes in the codebase +### Storybook doesn't detect changes in the codebase -If you are a Storybook contributor and still experience issues, it is recommended that you verify your local Storybook instance for any unintentional local changes. To do this, you can use the following command: +If you are a Storybook contributor and still experience issues, it is recommended that you verify your local Storybook instance for any unintentional local changes. To do this, you can use the following command: -```shell -git clean -dx --dry-run -``` +```shell +git clean -dx --dry-run +``` By executing this command, you can see which untracked or ignored files and directories will be removed from your working directory if you run it with the `--force` flag. Before running the command with the `--force` flag, please commit any local changes you want to keep. Otherwise, they will be lost. -## Contributing to Storybook +## Contributing to Storybook For further advice on contributing, please refer to our [NEW contributing guide on the Storybook website](https://storybook.js.org/docs/contribute). diff --git a/CONTRIBUTING.old.md b/CONTRIBUTING.old.md index e8cfb2842867..00c02784576c 100644 --- a/CONTRIBUTING.old.md +++ b/CONTRIBUTING.old.md @@ -31,8 +31,8 @@ - [Verify your local version is working](#verify-your-local-version-is-working) - [Documentation](#documentation) - [Release Guide](#release-guide) - - [Prerelease:](#prerelease) - - [Full release:](#full-release) + - [Prerelease:](#prerelease) + - [Full release:](#full-release) Thanks for your interest in improving Storybook! We are a community-driven project and welcome contributions of all kinds: from discussion to documentation to bugfixes to feature improvements. diff --git a/CONTRIBUTING/RELEASING.md b/CONTRIBUTING/RELEASING.md index 3fd24ae91d69..b2f8a7b82eaf 100644 --- a/CONTRIBUTING/RELEASING.md +++ b/CONTRIBUTING/RELEASING.md @@ -336,31 +336,31 @@ Done! 🚀 If you need to release a change to an older minor version that is not the latest, you have to do it manually, locally. The process is described below, with an example of releasing a new `v8.3.7` in a situation where `8.4.0` is currently the latest version. 1. Checkout the _existing_ tag that matches the latest minor release you want to bump, and create a new branch from it. In this case, we want to do: - 1. `git fetch --all --tags` - 2. `git checkout tags/v8.3.6 -b patch-8-3-7` + 1. `git fetch --all --tags` + 2. `git checkout tags/v8.3.6 -b patch-8-3-7` 2. Make the changes you need to, most likely cherry-picking commits from the fix you need to back-port. 3. Run `yarn install` 4. Build all packages in `code` with `yarn task --task compile --no-link` 5. Commit and push your changes. 6. Trigger _daily_ CI manually on your branch: - 1. Open [CircleCI](https://app.circleci.com/pipelines/github/storybookjs/storybook) and click "Trigger Pipeline" on the top right corner of the page. - 2. Set the following configuration options: - - Pipeline: _"storybook default"_ - - Config Source: _"storybook"_ - - Branch: Your branch, eg. `patch-8-3-7` - 3. Add a parameter, with _"name"_ `workflow`, _"value"_ `daily` + 1. Open [CircleCI](https://app.circleci.com/pipelines/github/storybookjs/storybook) and click "Trigger Pipeline" on the top right corner of the page. + 2. Set the following configuration options: + - Pipeline: _"storybook default"_ + - Config Source: _"storybook"_ + - Branch: Your branch, eg. `patch-8-3-7` + 3. Add a parameter, with _"name"_ `workflow`, _"value"_ `daily` 7. Wait for CI to finish successfully. 8. Bump all package versions: - 1. `cd scripts` - 2. `yarn release:version --release-type patch` -9. Commit with `git commit -m "Bump version from to MANUALLY"` + 1. `cd scripts` + 2. `yarn release:version --release-type patch` +9. Commit with `git commit -m "Bump version from to MANUALLY"` 10. Add a new entry to `CHANGELOG.md`, describing your changes 11. Commit with `git commit -m "Update CHANGELOG.md with MANUALLY"` 12. Ensure you have the correct write permissions for all the Storybook npm packages. You need to be an admin of the _storybook_ org, and the packages that are not in the org. The simplest way to check this is to ensure you can see the _"Settings"_ tab in the following packages: - 1. [`@storybook/react-vite`](https://www.npmjs.com/package/@storybook/react-vite/access) - 2. [`storybook`](https://www.npmjs.com/package/storybook/access) - 3. [`sb`](https://www.npmjs.com/package/sb/access) - 4. [`create-storybook`](https://www.npmjs.com/package/create-storybook/access) + 1. [`@storybook/react-vite`](https://www.npmjs.com/package/@storybook/react-vite/access) + 2. [`storybook`](https://www.npmjs.com/package/storybook/access) + 3. [`sb`](https://www.npmjs.com/package/sb/access) + 4. [`create-storybook`](https://www.npmjs.com/package/create-storybook/access) 13. Get your npm access token or generate a new one at https://www.npmjs.com/settings/your-username/tokens. Remember to give it access to the `storybook` org and the packages not in the org, as listed above. 14. Publish all packages with `YARN_NPM_AUTH_TOKEN= yarn release:publish --tag tag-for-publishing-older-releases --verbose` - It goes through all packages and publishes them. If any number of packages fails to publish, it will retry 5 times, skipping those that have already been published. @@ -371,16 +371,16 @@ If you need to release a change to an older minor version that is not the latest 4. [`create-storybook`](https://www.npmjs.com/package/create-storybook?activeTab=versions) 16. Push 17. Manually create a GitHub Release at https://github.com/storybookjs/storybook/releases/new with: - 1. Create new tag: `v` (e.g., `v8.3.7`) - 2. Target: your branch (e.g., `patch-8-3-7`) - 3. Previous tag: `v` (e.g., `v8.3.6`) - 4. Title: `v` (e.g., `v8.3.7`) - 5. Description: The content you added to `CHANGELOG.md` - 6. Untick _"Set as the latest release"_ -18. Cherry-pick your changelog changes into `next`, so they are actually visible - 1. Checkout the `next` branch - 2. Cherry-pick the commit you created with your changelog modifications - 3. Push + 1. Create new tag: `v` (e.g., `v8.3.7`) + 2. Target: your branch (e.g., `patch-8-3-7`) + 3. Previous tag: `v` (e.g., `v8.3.6`) + 4. Title: `v` (e.g., `v8.3.7`) + 5. Description: The content you added to `CHANGELOG.md` + 6. Untick _"Set as the latest release"_ +18. Cherry-pick your changelog changes into `next`, so they are actually visible + 1. Checkout the `next` branch + 2. Cherry-pick the commit you created with your changelog modifications + 3. Push Done. 🎉 diff --git a/MIGRATION.md b/MIGRATION.md index 3116c8886723..fdea3696a405 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -206,17 +206,17 @@ - [Tab addons cannot manually route, Tool addons can filter their visibility via tabId](#tab-addons-cannot-manually-route-tool-addons-can-filter-their-visibility-via-tabid) - [Removed `config` preset](#removed-config-preset-1) - [From version 7.5.0 to 7.6.0](#from-version-750-to-760) - - [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated) - - [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated) - - [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated) - - [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop) - - [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react) + - [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated) + - [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated) + - [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated) + - [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop) + - [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react) - [From version 7.4.0 to 7.5.0](#from-version-740-to-750) - - [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated) - - [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers) + - [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated) + - [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers) - [From version 7.0.0 to 7.2.0](#from-version-700-to-720) - - [Addon API is more type-strict](#addon-api-is-more-type-strict) - - [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated) + - [Addon API is more type-strict](#addon-api-is-more-type-strict) + - [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated) - [From version 6.5.x to 7.0.0](#from-version-65x-to-700) - [7.0 breaking changes](#70-breaking-changes) - [Dropped support for Node 15 and below](#dropped-support-for-node-15-and-below) @@ -242,7 +242,7 @@ - [Deploying build artifacts](#deploying-build-artifacts) - [Dropped support for file URLs](#dropped-support-for-file-urls) - [Serving with nginx](#serving-with-nginx) - - [Ignore story files from node\_modules](#ignore-story-files-from-node_modules) + - [Ignore story files from node_modules](#ignore-story-files-from-node_modules) - [7.0 Core changes](#70-core-changes) - [7.0 feature flags removed](#70-feature-flags-removed) - [Story context is prepared before for supporting fine grained updates](#story-context-is-prepared-before-for-supporting-fine-grained-updates) @@ -256,7 +256,7 @@ - [Addon-interactions: Interactions debugger is now default](#addon-interactions-interactions-debugger-is-now-default) - [7.0 Vite changes](#70-vite-changes) - [Vite builder uses Vite config automatically](#vite-builder-uses-vite-config-automatically) - - [Vite cache moved to node\_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook) + - [Vite cache moved to node_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook) - [7.0 Webpack changes](#70-webpack-changes) - [Webpack4 support discontinued](#webpack4-support-discontinued) - [Babel mode v7 exclusively](#babel-mode-v7-exclusively) @@ -307,7 +307,7 @@ - [Dropped addon-docs manual babel configuration](#dropped-addon-docs-manual-babel-configuration) - [Dropped addon-docs manual configuration](#dropped-addon-docs-manual-configuration) - [Autoplay in docs](#autoplay-in-docs) - - [Removed STORYBOOK\_REACT\_CLASSES global](#removed-storybook_react_classes-global) + - [Removed STORYBOOK_REACT_CLASSES global](#removed-storybook_react_classes-global) - [7.0 Deprecations and default changes](#70-deprecations-and-default-changes) - [storyStoreV7 enabled by default](#storystorev7-enabled-by-default) - [`Story` type deprecated](#story-type-deprecated) @@ -2669,7 +2669,7 @@ The deprecated properties `tooltipShown`, `closeOnClick`, and `onVisibilityChang onVisibilityChange // becomes onVisibleChange > ... -; + ``` #### LinkTo direct import from addon-links diff --git a/code/.oxfmtrc.json b/code/.oxfmtrc.json deleted file mode 100644 index 5de8a18c6d3d..000000000000 --- a/code/.oxfmtrc.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "printWidth": 100, - "tabWidth": 2, - "bracketSpacing": true, - "trailingComma": "es5", - "singleQuote": true, - "arrowParens": "always", - "sortPackageJson": false, - "ignorePatterns": [ - "*.mdx", - "*.md", - "*.bundle.js", - "*.js.map", - ".yarn", - ".vscode", - ".nx/cache", - ".nx/workspace-data", - "dist", - "build", - "bench", - "coverage", - "node_modules", - "storybook-static", - "built-storybooks", - "ember-output", - "core/assets", - "core/report", - "core/src/core-server/presets/common-manager.ts", - "core/src/core-server/utils/__search-files-tests__", - "core/src/core-server/utils/__mockdata__/src/Empty.stories.ts", - "lib/codemod/src/transforms/__testfixtures__", - "**/frameworks/angular/template/**" - ] -} diff --git a/code/addons/docs/docs/frameworks/ANGULAR.md b/code/addons/docs/docs/frameworks/ANGULAR.md index 386a38ca517c..81211e5ba2a9 100644 --- a/code/addons/docs/docs/frameworks/ANGULAR.md +++ b/code/addons/docs/docs/frameworks/ANGULAR.md @@ -81,8 +81,8 @@ Then you'll need to configure Compodoc to generate a `documentation.json` file. "json", "-d", "." // the root folder of your project - ], - }, + ] + } "build-storybook": { ..., "compodoc": true, diff --git a/code/addons/docs/src/blocks/blocks/Unstyled.mdx b/code/addons/docs/src/blocks/blocks/Unstyled.mdx index 667621c83eb8..31375e8a0a01 100644 --- a/code/addons/docs/src/blocks/blocks/Unstyled.mdx +++ b/code/addons/docs/src/blocks/blocks/Unstyled.mdx @@ -17,18 +17,19 @@ import { Unstyled } from '@storybook/addon-docs/blocks'; ... and so will this paragraph. - # This heading will not be styled + +# This heading will not be styled

Neither will this subheading

- > This block quote will not be styled +> This block quote will not be styled - ... neither will this paragraph, nor the following component: - +... neither will this paragraph, nor the following component: +
``` -Yields: +Yields: # This heading will be styled @@ -39,11 +40,13 @@ Yields: ... and so will this paragraph. - # This heading will not be styled -

Neither will this subheading

+# This heading will not be styled + +

Neither will this subheading

+ +> This block quote will not be styled - > This block quote will not be styled +... neither will this paragraph, nor the following component: - ... neither will this paragraph, nor the following component:
diff --git a/code/addons/docs/template/stories/docs2/Error.mdx b/code/addons/docs/template/stories/docs2/Error.mdx index 28bb44eb1946..59cecece15d7 100644 --- a/code/addons/docs/template/stories/docs2/Error.mdx +++ b/code/addons/docs/template/stories/docs2/Error.mdx @@ -1,3 +1,3 @@ {/* This file intentionally has an error */} - \ No newline at end of file + diff --git a/code/addons/docs/template/stories/docs2/ResolvedReact.mdx b/code/addons/docs/template/stories/docs2/ResolvedReact.mdx index d937d3ec2664..3d0cdcd50657 100644 --- a/code/addons/docs/template/stories/docs2/ResolvedReact.mdx +++ b/code/addons/docs/template/stories/docs2/ResolvedReact.mdx @@ -4,7 +4,7 @@ import * as ReactDom from 'react-dom'; import * as ReactDomServer from 'react-dom/server'; import { ResolvedReact } from './ResolvedReact'; - + This doc is used to display the resolved version of React and its related packages. As long as `@storybook/addon-docs` is installed, `react` and `react-dom` should be available to import from and should resolve to the same version. @@ -18,11 +18,16 @@ The MDX here ensures that it works in an MDX file. ## In MDX -react: {ReactExport.version ?? 'no version export found'} +react: +{ReactExport.version ?? 'no version export found'} -react-dom: {ReactDom.version ?? 'no version export found'} +react-dom: +{ReactDom.version ?? 'no version export found'} -react-dom/server: {ReactDomServer.version ?? 'no version export found'} +react-dom/server: + + {ReactDomServer.version ?? 'no version export found'} + ## In `ResolvedReact` component diff --git a/code/addons/docs/template/stories/docs2/Tags.mdx b/code/addons/docs/template/stories/docs2/Tags.mdx index 3355304e5015..2e645046775a 100644 --- a/code/addons/docs/template/stories/docs2/Tags.mdx +++ b/code/addons/docs/template/stories/docs2/Tags.mdx @@ -4,4 +4,4 @@ import { Meta } from '@storybook/addon-docs/blocks'; # Docs with tags -hello docs \ No newline at end of file +hello docs diff --git a/code/addons/docs/template/stories/docs2/UtfSymbolScroll.mdx b/code/addons/docs/template/stories/docs2/UtfSymbolScroll.mdx index d8aa64b1bf1c..331013a8ee24 100644 --- a/code/addons/docs/template/stories/docs2/UtfSymbolScroll.mdx +++ b/code/addons/docs/template/stories/docs2/UtfSymbolScroll.mdx @@ -1,14 +1,14 @@ -import { Meta } from '@storybook/addon-docs/blocks'; - - - -## Instruction - -> Instruction below works only in iframe.html. Unknown code in normal mode (with manager) removes hash from url. - -Click on [link](#anchor-with-utf-symbols-абвг). That will jump scroll to anchor after green block below. Then reload page and -it should smooth-scroll to that anchor. - -
Space for scroll test
- -## Anchor with utf symbols (абвг) \ No newline at end of file +import { Meta } from '@storybook/addon-docs/blocks'; + + + +## Instruction + +> Instruction below works only in iframe.html. Unknown code in normal mode (with manager) removes hash from url. + +Click on [link](#anchor-with-utf-symbols-абвг). That will jump scroll to anchor after green block below. Then reload page and +it should smooth-scroll to that anchor. + +
Space for scroll test
+ +## Anchor with utf symbols (абвг) diff --git a/code/builders/builder-vite/input/iframe.html b/code/builders/builder-vite/input/iframe.html index 1637f04eb9e8..15e37a0ae3ad 100644 --- a/code/builders/builder-vite/input/iframe.html +++ b/code/builders/builder-vite/input/iframe.html @@ -65,30 +65,30 @@
+```svelte + + +
+ {count} + + +
+``` + +The same setup works with Svelte stories files too, providing both type safety and autocompletion. - The same setup works with Svelte stories files too, providing both type safety and autocompletion. @@ -189,7 +195,7 @@ const meta = preview.type<{ args: MyComponentProps }>().meta({ ```ts declare global { interface HTMLElementTagNameMap { - "my-element": MyElement; + 'my-element': MyElement; } } ``` @@ -211,4 +217,4 @@ const meta = preview.type<{ args: MyElementProps }>().meta({ }); ``` - \ No newline at end of file + diff --git a/docs/writing-tests/accessibility-testing.mdx b/docs/writing-tests/accessibility-testing.mdx index acc4852c1a0d..f2bf56ba6694 100644 --- a/docs/writing-tests/accessibility-testing.mdx +++ b/docs/writing-tests/accessibility-testing.mdx @@ -45,7 +45,7 @@ The full installation instructions, including project requirements, are availabl ## Check for violations -When you navigate to a story, automated accessibility checks are run and the results are reported in the Accessibility addon panel. +When you navigate to a story, automated accessibility checks are run and the results are reported in the Accessibility addon panel. The results are broken down into three sub-tabs: @@ -57,13 +57,13 @@ The results are broken down into three sub-tabs: Because the addon is built on top of `axe-core`, much of the configuration available maps to its available options: -| Property | Default | Description | -| ------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `parameters.a11y.context` | `'body'` | [Context](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter) passed to `axe.run`. Defines which elements to run checks against. | +| Property | Default | Description | +| ------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `parameters.a11y.context` | `'body'` | [Context](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter) passed to `axe.run`. Defines which elements to run checks against. | | `parameters.a11y.config` | (see below) | Configuration passed to [`axe.configure()`](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure). Most commonly used to [configure individual rules](#individual-rules). | -| `parameters.a11y.options` | `{}` | [Options](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter) passed to `axe.run`. Can be used to adjust the rulesets checked against. | -| `parameters.a11y.test` | `undefined` | Determines test behavior when run with the Vitest addon. [More details below](#test-behavior). | -| `globals.a11y.manual` | `undefined` | Set to `true` to prevent stories from being automatically analyzed when visited. [More details below](#disable-automated-checks) | +| `parameters.a11y.options` | `{}` | [Options](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter) passed to `axe.run`. Can be used to adjust the rulesets checked against. | +| `parameters.a11y.test` | `undefined` | Determines test behavior when run with the Vitest addon. [More details below](#test-behavior). | +| `globals.a11y.manual` | `undefined` | Set to `true` to prevent stories from being automatically analyzed when visited. [More details below](#disable-automated-checks) |
Default `parameters.a11y.config` @@ -76,8 +76,8 @@ By default, Storybook disables the [region rule](https://dequeuniversity.com/rul { id: 'region', enabled: false, - } - ] + }, + ]; } ``` @@ -149,13 +149,13 @@ When you disable automated accessibility checks, the addon will not run any test - If you are using Svelte CSF, you can turn off automated accessibility checks for stories or components by adding globals to your story or adjusting the `defineMeta` function with the required configuration. With a regular CSF story, you can add the following to your stories or meta (or default export): +If you are using Svelte CSF, you can turn off automated accessibility checks for stories or components by adding globals to your story or adjusting the `defineMeta` function with the required configuration. With a regular CSF story, you can add the following to your stories or meta (or default export): - Disable automated accessibility checks for stories or components by adding the following globals to your stories or meta (or default export): +Disable automated accessibility checks for stories or components by adding the following globals to your stories or meta (or default export): @@ -213,18 +213,12 @@ If you cannot use the Vitest addon, you can still run your tests in CI using the You can use configuration to progressively work toward a more accessible UI by combining multiple test behaviors. For example, you can start with `'error'` to fail on accessibility violations, then switch to `'todo'` to mark components that need fixing, and finally remove the todos once all stories pass accessibility tests: 1. Update your project configuration to fail on accessibility violations by setting [`parameters.a11y.test`](#test-behavior) to `'error'`. This ensures that all new stories are tested to meet accessibility standards. - - 2. You will likely find that many components have accessibility failures (and maybe feel a bit overwhelmed!). 3. Take note of the components with accessibility issues and temporarily reduce their failures to warnings by applying the `'todo'` parameter value. This keeps accessibility issues visible while not blocking development. This is also a good time to commit your work as a baseline for future improvements. - - 4. Pick a good starting point from the components you just marked `'todo'` (we recommend something like Button, for its simplicity and likelihood of being used within other components). Fix the issues in that component using the suggestions in the addon panel to ensure it passes accessibility tests, then remove the parameter. - - 5. Pick another component and repeat the process until you've covered all your components and you're an accessibility hero! ## FAQ @@ -249,12 +243,12 @@ To enable this feature flag, add the following configuration to your `.storybook **More testing resources** -* [Vitest addon](./integrations/vitest-addon/index.mdx) for running tests in Storybook -* [Interaction testing](./interaction-testing.mdx) for user behavior simulation -* [Visual testing](./visual-testing.mdx) for appearance -* [Snapshot testing](./snapshot-testing.mdx) for rendering errors and warnings -* [Test coverage](./test-coverage.mdx) for measuring code coverage -* [CI](./in-ci.mdx) for running tests in your CI/CD pipeline -* [End-to-end testing](./integrations/stories-in-end-to-end-tests.mdx) for simulating real user scenarios -* [Unit testing](./integrations/stories-in-unit-tests.mdx) for functionality -* [Test runner](./integrations/test-runner.mdx) to automate test execution +- [Vitest addon](./integrations/vitest-addon/index.mdx) for running tests in Storybook +- [Interaction testing](./interaction-testing.mdx) for user behavior simulation +- [Visual testing](./visual-testing.mdx) for appearance +- [Snapshot testing](./snapshot-testing.mdx) for rendering errors and warnings +- [Test coverage](./test-coverage.mdx) for measuring code coverage +- [CI](./in-ci.mdx) for running tests in your CI/CD pipeline +- [End-to-end testing](./integrations/stories-in-end-to-end-tests.mdx) for simulating real user scenarios +- [Unit testing](./integrations/stories-in-unit-tests.mdx) for functionality +- [Test runner](./integrations/test-runner.mdx) to automate test execution diff --git a/docs/writing-tests/in-ci.mdx b/docs/writing-tests/in-ci.mdx index 0bd8aa72b12e..96bd8e613ed7 100644 --- a/docs/writing-tests/in-ci.mdx +++ b/docs/writing-tests/in-ci.mdx @@ -27,7 +27,7 @@ Let’s go step-by-step to set things up. For convenience, define a script in your `package.json` to run the Storybook tests. This is the same command you would run locally, but it’s useful to have it in your CI workflow. ```json title="package.json" -{ +{ "scripts": { "test-storybook": "vitest --project=storybook" } @@ -61,18 +61,19 @@ jobs: image: mcr.microsoft.com/playwright:v1.58.2-noble steps: - uses: actions/checkout@v4 - + - name: Setup Node uses: actions/setup-node@v4 with: node-version: 22.12.0 - + - name: Install dependencies run: npm ci - + - name: Run tests run: npm run test-storybook ``` +
@@ -80,7 +81,7 @@ jobs: Create a file in the root of your repo, `.gitlab-ci.yml`: -```yaml title=".gitlab-ci.yml" +```yaml title=".gitlab-ci.yml" image: node:jod stages: @@ -102,6 +103,7 @@ Test: script: - npm run test-storybook ``` +
@@ -119,10 +121,10 @@ definitions: pipelines: default: - stage: - name: "UI Tests" + name: 'UI Tests' steps: - step: - name: "Run Tests" + name: 'Run Tests' # Make sure to grab the latest version of the Playwright image # https://playwright.dev/docs/docker#pull-the-image image: mcr.microsoft.com/playwright:v1.58.2-noble @@ -134,6 +136,7 @@ pipelines: - npm ci - npm run test-storybook ``` +
@@ -143,7 +146,7 @@ Create a file in the root of your repo, `.circleci/config.yml`: ```yaml title=".circleci/config.yml" version: 2.1 - + executors: ui-testing: docker: @@ -175,6 +178,7 @@ workflows: jobs: - Test ``` +
@@ -198,10 +202,11 @@ cache: npm jobs: include: - - stage: "UI Tests" - name: "Run tests" + - stage: 'UI Tests' + name: 'Run tests' script: npm run test-storybook ``` +
@@ -235,6 +240,7 @@ pipeline { } } ``` +
@@ -247,14 +253,14 @@ trigger: - main pool: - vmImage: "ubuntu-latest" + vmImage: 'ubuntu-latest' stages: - stage: UI_Tests - displayName: "UI Tests" + displayName: 'UI Tests' jobs: - job: Test - displayName: "Storybook tests" + displayName: 'Storybook tests' # Make sure to grab the latest version of the Playwright image # https://playwright.dev/docs/docker#pull-the-image container: mcr.microsoft.com/playwright:v1.58.2-noble @@ -262,11 +268,11 @@ stages: npm_config_cache: $(Pipeline.Workspace)/.npm steps: - task: UseNode@1 - displayName: "Install Node.js" + displayName: 'Install Node.js' inputs: - version: "22.12.0" + version: '22.12.0' - task: Cache@2 - displayName: "Install and cache dependencies" + displayName: 'Install and cache dependencies' inputs: key: 'npm | "$(Agent.OS)" | package-lock.json' restoreKeys: | @@ -275,11 +281,12 @@ stages: - script: npm ci condition: ne(variables.CACHE_RESTORED, 'true') - task: CmdLine@2 - displayName: "Run tests" + displayName: 'Run tests' inputs: script: npm run test-storybook ``` -
+ +
@@ -318,7 +325,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 22.12.0 - + - name: Install dependencies run: npm ci @@ -354,7 +361,7 @@ You can either adjust the command in your `package.json` scripts: **For all tests:** ```diff title="package.json" -{ +{ "scripts": { + "test": "vitest --coverage" - "test": "vitest" @@ -365,7 +372,7 @@ You can either adjust the command in your `package.json` scripts: **For only Storybook tests:** ```diff title="package.json" -{ +{ "scripts": { + "test-storybook": "vitest --project=storybook --coverage" - "test-storybook": "vitest --project=storybook" @@ -410,7 +417,7 @@ Some projects have other tests run via Vitest, e.g. unit tests, in addition to t You can run these tests independently by specifying the project filter in a separate script. For example, for a Vitest project called “unit”: ```json title="package.json" -{ +{ "scripts": { "test-storybook": "vitest --project=storybook", "test-unit": "vitest --project=unit" @@ -430,7 +437,7 @@ Then, in your workflow, call this script alongside the Storybook one: You may also choose to run all tests together by simply omitting the `--project=storybook` filter from the `package.json` script: ```json title="package.json" -{ +{ "scripts": { "test": "vitest" } @@ -447,12 +454,12 @@ The workflow would then look like: **More testing resources** -* [Vitest addon](./integrations/vitest-addon/index.mdx) for running tests in Storybook -* [Interaction testing](./interaction-testing.mdx) for user behavior simulation -* [Accessibility testing](./accessibility-testing.mdx) for accessibility -* [Visual testing](./visual-testing.mdx) for appearance -* [Snapshot testing](./snapshot-testing.mdx) for rendering errors and warnings -* [Test coverage](./test-coverage.mdx) for measuring code coverage -* [End-to-end testing](./integrations/stories-in-end-to-end-tests.mdx) for simulating real user scenarios -* [Unit testing](./integrations/stories-in-unit-tests.mdx) for functionality -* [Test runner](./integrations/test-runner.mdx) to automate test execution +- [Vitest addon](./integrations/vitest-addon/index.mdx) for running tests in Storybook +- [Interaction testing](./interaction-testing.mdx) for user behavior simulation +- [Accessibility testing](./accessibility-testing.mdx) for accessibility +- [Visual testing](./visual-testing.mdx) for appearance +- [Snapshot testing](./snapshot-testing.mdx) for rendering errors and warnings +- [Test coverage](./test-coverage.mdx) for measuring code coverage +- [End-to-end testing](./integrations/stories-in-end-to-end-tests.mdx) for simulating real user scenarios +- [Unit testing](./integrations/stories-in-unit-tests.mdx) for functionality +- [Test runner](./integrations/test-runner.mdx) to automate test execution diff --git a/docs/writing-tests/index.mdx b/docs/writing-tests/index.mdx index 099008cb5f9a..e36448300d96 100644 --- a/docs/writing-tests/index.mdx +++ b/docs/writing-tests/index.mdx @@ -65,20 +65,19 @@ If you are already running `vitest` as part of your CI then your stories should If you’re not yet running Vitest in CI, you should set that up. First by adding a new script to your `package.json`: ```json title="package.json" -{ +{ "scripts": { "test-storybook": "vitest --project=storybook" } -} +} ``` Note that this assumes you have a Vitest project called “storybook” for your stories, which is the default configuration when you install Storybook Test. If you’ve renamed it, adjust the script accordingly. -Next, add a new CI workflow. +Next, add a new CI workflow.
-If you use Github Actions that would look like: - + If you use Github Actions that would look like: ```yaml title=".github/workflows/test-storybook.yml" name: Storybook Tests @@ -161,9 +160,9 @@ Storybook Test supports a variety of testing types to help you validate your wor ### Render tests -The most important tool for testing your components in Storybook is stories that render your components in various states. +The most important tool for testing your components in Storybook is stories that render your components in various states. -However, you might not be aware that a basic story is also a [smoke test](https://en.wikipedia.org/wiki/Smoke_testing_(software)), which we call a **render test**. The test passes when the story renders successfully and fails when it errors. +However, you might not be aware that a basic story is also a [smoke test](), which we call a **render test**. The test passes when the story renders successfully and fails when it errors. ![Storybook app showing a failing render test](../_assets/writing-tests/interactions-test-failure-render.png) @@ -175,7 +174,7 @@ Render tests are a very basic kind of interaction test. To test stateful compone -But `play` functions can also be used for setting up state, creating spies, mocking out the network, simulating user interactions with your components, asserting output, and more. They are the meat and potatoes of testing and are the foundation for the rest of your testing journey in Storybook. +But `play` functions can also be used for setting up state, creating spies, mocking out the network, simulating user interactions with your components, asserting output, and more. They are the meat and potatoes of testing and are the foundation for the rest of your testing journey in Storybook. Here’s a more complex example, which includes [spying and mocking](./interaction-testing.mdx#spying-on-functions-with-fn) via the `fn` utility. @@ -227,13 +226,13 @@ If you prefer, you can reuse your Storybook stories in a traditional testing env **More testing resources** -* [Vitest addon](./integrations/vitest-addon/index.mdx) for running tests in Storybook -* [Interaction testing](./interaction-testing.mdx) for user behavior simulation -* [Accessibility testing](./accessibility-testing.mdx) for accessibility -* [Visual testing](./visual-testing.mdx) for appearance -* [Snapshot testing](./snapshot-testing.mdx) for rendering errors and warnings -* [Test coverage](./test-coverage.mdx) for measuring code coverage -* [CI](./in-ci.mdx) for running tests in your CI/CD pipeline -* [End-to-end testing](./integrations/stories-in-end-to-end-tests.mdx) for simulating real user scenarios -* [Unit testing](./integrations/stories-in-unit-tests.mdx) for functionality -* [Test runner](./integrations/test-runner.mdx) to automate test execution +- [Vitest addon](./integrations/vitest-addon/index.mdx) for running tests in Storybook +- [Interaction testing](./interaction-testing.mdx) for user behavior simulation +- [Accessibility testing](./accessibility-testing.mdx) for accessibility +- [Visual testing](./visual-testing.mdx) for appearance +- [Snapshot testing](./snapshot-testing.mdx) for rendering errors and warnings +- [Test coverage](./test-coverage.mdx) for measuring code coverage +- [CI](./in-ci.mdx) for running tests in your CI/CD pipeline +- [End-to-end testing](./integrations/stories-in-end-to-end-tests.mdx) for simulating real user scenarios +- [Unit testing](./integrations/stories-in-unit-tests.mdx) for functionality +- [Test runner](./integrations/test-runner.mdx) to automate test execution diff --git a/docs/writing-tests/integrations/index.mdx b/docs/writing-tests/integrations/index.mdx index f2e7b0d9e707..84ce3e5fe8ec 100644 --- a/docs/writing-tests/integrations/index.mdx +++ b/docs/writing-tests/integrations/index.mdx @@ -3,4 +3,4 @@ title: Integrations sidebar: order: 7 title: Integrations ---- \ No newline at end of file +--- diff --git a/docs/writing-tests/integrations/stories-in-end-to-end-tests.mdx b/docs/writing-tests/integrations/stories-in-end-to-end-tests.mdx index 67469a26fbfd..615218d3c85f 100644 --- a/docs/writing-tests/integrations/stories-in-end-to-end-tests.mdx +++ b/docs/writing-tests/integrations/stories-in-end-to-end-tests.mdx @@ -20,7 +20,9 @@ An example of an end-to-end test with Cypress and Storybook is testing a login c {/* prettier-ignore-end */} - The play function contains small snippets of code that run after the story renders. It allows you to sequence interactions in stories. + +The play function contains small snippets of code that run after the story renders. It allows you to sequence interactions in stories. + With Cypress, you could write the following test: @@ -46,7 +48,9 @@ A real-life scenario of user flow testing with Playwright would be how to test a {/* prettier-ignore-end */} - The play function contains small snippets of code that run after the story renders. It allows you to sequence interactions in stories. + +The play function contains small snippets of code that run after the story renders. It allows you to sequence interactions in stories. + With Playwright, you can write a test to check if the inputs are filled and match the story: @@ -61,12 +65,12 @@ Once you execute Playwright, it opens a new browser window, loads Storybook's is **More testing resources** -* [Interaction testing](../interaction-testing.mdx) for user behavior simulation -* [Accessibility testing](../accessibility-testing.mdx) for accessibility -* [Visual testing](../visual-testing.mdx) for appearance -* [Snapshot testing](../snapshot-testing.mdx) for rendering errors and warnings -* [Test coverage](../test-coverage.mdx) for measuring code coverage -* [CI](../in-ci.mdx) for running tests in your CI/CD pipeline -* [Vitest addon](./vitest-addon/index.mdx) for running tests in Storybook -* [Test runner](./test-runner.mdx) to automate test execution -* [Unit testing](./stories-in-unit-tests.mdx) for functionality +- [Interaction testing](../interaction-testing.mdx) for user behavior simulation +- [Accessibility testing](../accessibility-testing.mdx) for accessibility +- [Visual testing](../visual-testing.mdx) for appearance +- [Snapshot testing](../snapshot-testing.mdx) for rendering errors and warnings +- [Test coverage](../test-coverage.mdx) for measuring code coverage +- [CI](../in-ci.mdx) for running tests in your CI/CD pipeline +- [Vitest addon](./vitest-addon/index.mdx) for running tests in Storybook +- [Test runner](./test-runner.mdx) to automate test execution +- [Unit testing](./stories-in-unit-tests.mdx) for functionality diff --git a/docs/writing-tests/integrations/stories-in-unit-tests.mdx b/docs/writing-tests/integrations/stories-in-unit-tests.mdx index be42ec2d3496..da0f5689f3e1 100644 --- a/docs/writing-tests/integrations/stories-in-unit-tests.mdx +++ b/docs/writing-tests/integrations/stories-in-unit-tests.mdx @@ -70,47 +70,51 @@ If you intend to test multiple stories in a single test, use the [`composeStorie Storybook provides community-led addons for other frameworks like [Vue 2](https://storybook.js.org/addons/@storybook/testing-vue) and [Angular](https://storybook.js.org/addons/@storybook/testing-angular). However, these addons still lack support for the latest stable Storybook release. If you're interested in helping out, we recommend reaching out to the maintainers using the default communication channels (GitHub and [Discord server](https://discord.com/channels/486522875931656193/839297503446695956)). - ### The args are not being passed to the test - The components returned by `composeStories` or `composeStory` not only can be rendered as React components but also come with the combined properties from the story, meta, and global configuration. This means that if you want to access args or parameters, for instance, you can do so: +### The args are not being passed to the test - {/* prettier-ignore-start */} +The components returned by `composeStories` or `composeStory` not only can be rendered as React components but also come with the combined properties from the story, meta, and global configuration. This means that if you want to access args or parameters, for instance, you can do so: - +{/* prettier-ignore-start */} + + - {/* prettier-ignore-end */} +{/* prettier-ignore-end */} - ### Next.js Vite cannot find the module +### Next.js Vite cannot find the module - If you are seeing error messages like `Cannot find module 'sb-original/image-context'` ensure you have included `storybookNextJsPlugin`. +If you are seeing error messages like `Cannot find module 'sb-original/image-context'` ensure you have included `storybookNextJsPlugin`. + +{/* prettier-ignore-start */} - {/* prettier-ignore-start */} + - +{/* prettier-ignore-end */} - {/* prettier-ignore-end */} - ### The args are not being passed to the test - When using the `composeStories` or `composeStory` functions, the components being rendered will have a combination of properties from the story, meta, and global configuration. Therefore, if you need to access the args or parameters, you can do so as follows: +### The args are not being passed to the test + +When using the `composeStories` or `composeStory` functions, the components being rendered will have a combination of properties from the story, meta, and global configuration. Therefore, if you need to access the args or parameters, you can do so as follows: - {/* prettier-ignore-start */} +{/* prettier-ignore-start */} - + + +{/* prettier-ignore-end */} - {/* prettier-ignore-end */} **More testing resources** -* [Interaction testing](../interaction-testing.mdx) for user behavior simulation -* [Accessibility testing](../accessibility-testing.mdx) for accessibility -* [Visual testing](../visual-testing.mdx) for appearance -* [Snapshot testing](../snapshot-testing.mdx) for rendering errors and warnings -* [Test coverage](../test-coverage.mdx) for measuring code coverage -* [CI](../in-ci.mdx) for running tests in your CI/CD pipeline -* [Vitest addon](./vitest-addon/index.mdx) for running tests in Storybook -* [Test runner](./test-runner.mdx) to automate test execution -* [End-to-end testing](./stories-in-end-to-end-tests.mdx) for simulating real user scenarios +- [Interaction testing](../interaction-testing.mdx) for user behavior simulation +- [Accessibility testing](../accessibility-testing.mdx) for accessibility +- [Visual testing](../visual-testing.mdx) for appearance +- [Snapshot testing](../snapshot-testing.mdx) for rendering errors and warnings +- [Test coverage](../test-coverage.mdx) for measuring code coverage +- [CI](../in-ci.mdx) for running tests in your CI/CD pipeline +- [Vitest addon](./vitest-addon/index.mdx) for running tests in Storybook +- [Test runner](./test-runner.mdx) to automate test execution +- [End-to-end testing](./stories-in-end-to-end-tests.mdx) for simulating real user scenarios diff --git a/docs/writing-tests/integrations/test-runner.mdx b/docs/writing-tests/integrations/test-runner.mdx index 939cab57f451..e343a2e93590 100644 --- a/docs/writing-tests/integrations/test-runner.mdx +++ b/docs/writing-tests/integrations/test-runner.mdx @@ -17,8 +17,8 @@ If you are using a Vite-powered Storybook framework, we recommend using the Vite Storybook test runner turns all of your stories into executable tests. It is powered by [Jest](https://jestjs.io/) and [Playwright](https://playwright.dev/). -* For those [without a play function](../../writing-stories/index.mdx): it verifies whether the story renders without any errors. -* For those [with a play function](../../writing-stories/play-function.mdx): it also checks for errors in the play function and that all assertions passed. +- For those [without a play function](../../writing-stories/index.mdx): it verifies whether the story renders without any errors. +- For those [with a play function](../../writing-stories/play-function.mdx): it also checks for errors in the play function and that all assertions passed. These tests run in a live browser and can be executed via the [command line](#cli-options) or your [CI server](#set-up-ci-to-run-tests). @@ -53,7 +53,9 @@ Start your Storybook with: {/* prettier-ignore-end */} - Storybook's test runner requires either a locally running Storybook instance or a published Storybook to run all the existing tests. + +Storybook's test runner requires either a locally running Storybook instance or a published Storybook to run all the existing tests. + Finally, open a new terminal window and run the test-runner with: @@ -73,31 +75,31 @@ Test runner offers zero-config support for Storybook. However, you can run `test The test-runner is powered by [Jest](https://jestjs.io/) and accepts a subset of its [CLI options](https://jestjs.io/docs/cli) (for example, `--watch`, `--maxWorkers`). If you're already using any of those flags in your project, you should be able to migrate them into Storybook's test-runner without any issues. Listed below are all the available flags and examples of using them. -| Options | Description | -| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--help` | Output usage information
`test-storybook --help` | -| `-s`, `--index-json` | Run in index json mode. Automatically detected (requires a compatible Storybook)
`test-storybook --index-json` | -| `--no-index-json` | Disables index json mode
`test-storybook --no-index-json` | -| `-c`, `--config-dir [dir-name]` | Directory where to load Storybook configurations from
`test-storybook -c .storybook` | -| `--watch` | Run in watch mode
`test-storybook --watch` | -| `--watchAll` | Watch files for changes and rerun all tests when something changes.
`test-storybook --watchAll` | -| `--coverage` | Runs [coverage tests](#generate-code-coverage) on your stories and components
`test-storybook --coverage` | -| `--coverageDirectory` | Directory where to write coverage report output
`test-storybook --coverage --coverageDirectory coverage/ui/storybook` | -| `--url` | Define the URL to run tests in. Useful for custom Storybook URLs
`test-storybook --url http://the-storybook-url-here.com` | -| `--browsers` | Define browsers to run tests in. One or multiple of: chromium, firefox, webkit
`test-storybook --browsers firefox chromium` | -| `--maxWorkers [amount]` | Specifies the maximum number of workers the worker-pool will spawn for running tests
`test-storybook --maxWorkers=2` | -| `--testTimeout [amount]` | Defines the maximum time in milliseconds that a test can run before it is automatically marked as failed. Useful for long-running tests
`test-storybook --testTimeout=60000` | -| `--no-cache` | Disable the cache
`test-storybook --no-cache` | -| `--clearCache` | Deletes the Jest cache directory and then exits without running tests
`test-storybook --clearCache` | -| `--verbose` | Display individual test results with the test suite hierarchy
`test-storybook --verbose` | -| `-u`, `--updateSnapshot` | Use this flag to re-record every snapshot that fails during this test run
`test-storybook -u` | -| `--eject` | Creates a local configuration file to override defaults of the test-runner
`test-storybook --eject` | -| `--json` | Prints the test results in JSON. This mode will send all other test output and user messages to stderr.
`test-storybook --json` | -| `--outputFile` | Write test results to a file when the --json option is also specified.
`test-storybook --json --outputFile results.json` | -| `--junit` | Indicates that test information should be reported in a junit file.
`test-storybook --**junit**` | -| `--ci` | Instead of the regular behavior of storing a new snapshot automatically, it will fail the test and require Jest to be run with `--updateSnapshot`.
`test-storybook --ci` | -| `--shard [index/count]` | Requires CI. Splits the test suite execution into multiple machines
`test-storybook --shard=1/8` | -| `--failOnConsole` | Makes tests fail on browser console errors
`test-storybook --failOnConsole` | +| Options | Description | +| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--help` | Output usage information
`test-storybook --help` | +| `-s`, `--index-json` | Run in index json mode. Automatically detected (requires a compatible Storybook)
`test-storybook --index-json` | +| `--no-index-json` | Disables index json mode
`test-storybook --no-index-json` | +| `-c`, `--config-dir [dir-name]` | Directory where to load Storybook configurations from
`test-storybook -c .storybook` | +| `--watch` | Run in watch mode
`test-storybook --watch` | +| `--watchAll` | Watch files for changes and rerun all tests when something changes.
`test-storybook --watchAll` | +| `--coverage` | Runs [coverage tests](#generate-code-coverage) on your stories and components
`test-storybook --coverage` | +| `--coverageDirectory` | Directory where to write coverage report output
`test-storybook --coverage --coverageDirectory coverage/ui/storybook` | +| `--url` | Define the URL to run tests in. Useful for custom Storybook URLs
`test-storybook --url http://the-storybook-url-here.com` | +| `--browsers` | Define browsers to run tests in. One or multiple of: chromium, firefox, webkit
`test-storybook --browsers firefox chromium` | +| `--maxWorkers [amount]` | Specifies the maximum number of workers the worker-pool will spawn for running tests
`test-storybook --maxWorkers=2` | +| `--testTimeout [amount]` | Defines the maximum time in milliseconds that a test can run before it is automatically marked as failed. Useful for long-running tests
`test-storybook --testTimeout=60000` | +| `--no-cache` | Disable the cache
`test-storybook --no-cache` | +| `--clearCache` | Deletes the Jest cache directory and then exits without running tests
`test-storybook --clearCache` | +| `--verbose` | Display individual test results with the test suite hierarchy
`test-storybook --verbose` | +| `-u`, `--updateSnapshot` | Use this flag to re-record every snapshot that fails during this test run
`test-storybook -u` | +| `--eject` | Creates a local configuration file to override defaults of the test-runner
`test-storybook --eject` | +| `--json` | Prints the test results in JSON. This mode will send all other test output and user messages to stderr.
`test-storybook --json` | +| `--outputFile` | Write test results to a file when the --json option is also specified.
`test-storybook --json --outputFile results.json` | +| `--junit` | Indicates that test information should be reported in a junit file.
`test-storybook --**junit**` | +| `--ci` | Instead of the regular behavior of storing a new snapshot automatically, it will fail the test and require Jest to be run with `--updateSnapshot`.
`test-storybook --ci` | +| `--shard [index/count]` | Requires CI. Splits the test suite execution into multiple machines
`test-storybook --shard=1/8` | +| `--failOnConsole` | Makes tests fail on browser console errors
`test-storybook --failOnConsole` | | `--includeTags` | Experimental feature
Defines a subset of stories to be tested if they match the enabled [tags](#experimental-filter-tests).
`test-storybook --includeTags="test-only, pages"` | | `--excludeTags` | Experimental feature
Prevents stories from being tested if they match the provided [tags](#experimental-filter-tests).
`test-storybook --excludeTags="no-tests, tokens"` | | `--skipTags` | Experimental feature
Configures the test runner to skip running tests for stories that match the provided [tags](#experimental-filter-tests).
`test-storybook --skipTags="skip-test, layout"` | @@ -147,7 +149,9 @@ Add a new [configuration file](#test-hook-api) inside your Storybook directory w {/* prettier-ignore-end */} - The `postVisit` hook allows you to extend the test runner's default configuration. Read more about them [here](#test-hook-api). + +The `postVisit` hook allows you to extend the test runner's default configuration. Read more about them [here](#test-hook-api). + 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. @@ -251,11 +255,11 @@ By default, the [`@storybook/addon-coverage`](https://storybook.js.org/addons/@s
Vite options -| Options | Description | Type | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| Options | Description | Type | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | | `checkProd` | Configures the plugin to skip instrumentation in production environments
`options: { istanbul: { checkProd: true,}}` | `boolean` | -| `cwd` | Configures the working directory for the coverage tests.
Defaults to `process.cwd()`
`options: { istanbul: { cwd: process.cwd(),}}` | `string` | -| `cypress` | Replaces the `VITE_COVERAGE` environment variable with `CYPRESS_COVERAGE`.
Requires Cypress's [code coverage](https://docs.cypress.io/guides/tooling/code-coverage)
`options: { istanbul: { cypress: true,}}` | `boolean` | +| `cwd` | Configures the working directory for the coverage tests.
Defaults to `process.cwd()`
`options: { istanbul: { cwd: process.cwd(),}}` | `string` | +| `cypress` | Replaces the `VITE_COVERAGE` environment variable with `CYPRESS_COVERAGE`.
Requires Cypress's [code coverage](https://docs.cypress.io/guides/tooling/code-coverage)
`options: { istanbul: { cypress: true,}}` | `boolean` | | `exclude` | Overrides the [default exclude list](https://github.com/storybookjs/addon-coverage/blob/main/src/constants.ts) with the provided list of files or directories to exclude from coverage
`options: { istanbul: { exclude: ['**/stories/**'],}}` | `Array` or `string` | | `extension` | Extends the [default extension list](https://github.com/storybookjs/addon-coverage/blob/main/src/constants.ts) with the provided list of file extensions to include in coverage
`options: { istanbul: { extension: ['.js', '.cjs', '.mjs'],}}` | `Array` or `string` | | `forceBuildInstrument` | Configures the plugin to add instrumentation in build mode
`options: { istanbul: { forceBuildInstrument: true,}}` | `boolean` | @@ -268,12 +272,12 @@ By default, the [`@storybook/addon-coverage`](https://storybook.js.org/addons/@s
Webpack 5 options -| Options | Description | Type | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| Options | Description | Type | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | | `autoWrap` | Provides support for top-level return statements by wrapping the program code in a function
`options: { istanbul: { autoWrap: true,}}` | `boolean` | | `compact` | Condenses the output of the instrumented code. Useful for debugging
`options: { istanbul: { compact: false,}}` | `boolean` | | `coverageVariable` | Defines the global variable name that Istanbul will use to store coverage results
`options: { istanbul: { coverageVariable: '__coverage__',}}` | `string` | -| `cwd` | Configures the working directory for the coverage tests.
Defaults to `process.cwd()`
`options: { istanbul: { cwd: process.cwd(),}}` | `string` | +| `cwd` | Configures the working directory for the coverage tests.
Defaults to `process.cwd()`
`options: { istanbul: { cwd: process.cwd(),}}` | `string` | | `debug` | Enables the debug mode for additional logging information during the instrumentation process
`options: { istanbul: { debug: true,}}` | `boolean` | | `esModules` | Enables support for ES Module syntax
`options: { istanbul: { esModules: true,}}` | `boolean` | | `exclude` | Overrides the [default exclude list](https://github.com/storybookjs/addon-coverage/blob/main/src/constants.ts) with the provided list of files or directories to exclude from coverage
`options: { istanbul: { exclude: ['**/stories/**'],}}` | `Array` or `string` | @@ -311,7 +315,9 @@ If you're publishing your Storybook with services such as [Vercel](https://verce {/* prettier-ignore-end */} - The published Storybook must be publicly available for this example to work. We recommend running the test server using the recipe [below](#run-against-non-deployed-storybooks) if it requires authentication. + +The published Storybook must be publicly available for this example to work. We recommend running the test server using the recipe [below](#run-against-non-deployed-storybooks) if it requires authentication. + ### Run against non-deployed Storybooks @@ -325,7 +331,9 @@ You can use your CI provider (for example, [GitHub Actions](https://github.com/f {/* prettier-ignore-end */} - By default, Storybook outputs the [build](../../sharing/publish-storybook.mdx#build-storybook-as-a-static-web-application) to the `storybook-static` directory. If you're using a different build directory, you'll need to adjust the recipe accordingly. + +By default, Storybook outputs the [build](../../sharing/publish-storybook.mdx#build-storybook-as-a-static-web-application) to the `storybook-static` directory. If you're using a different build directory, you'll need to adjust the recipe accordingly. + ## Advanced configuration @@ -334,18 +342,20 @@ You can use your CI provider (for example, [GitHub Actions](https://github.com/f The test-runner renders a story and executes its [play function](../../writing-stories/play-function.mdx) if one exists. However, certain behaviors are impossible to achieve via the play function, which executes in the browser. For example, if you want the test-runner to take visual snapshots for you, this is possible via Playwright/Jest but must be executed in Node. -The test-runner exports test hooks that can be overridden globally to enable use cases like visual or DOM snapshots. These hooks give you access to the test lifecycle *before* and *after* the story is rendered. +The test-runner exports test hooks that can be overridden globally to enable use cases like visual or DOM snapshots. These hooks give you access to the test lifecycle _before_ and _after_ the story is rendered. Listed below are the available hooks and an overview of how to use them. -| Hook | Description | -| ----------- | --------------------------------------------------------------------------------------------------------------- | +| Hook | Description | +| ----------- | ---------------------------------------------------------------------------------------------------------------- | | `prepare` | Prepares the browser for tests
`async prepare({ page, browserContext, testRunnerConfig }) {}` | | `setup` | Executes once before all the tests run
`setup() {}` | | `preVisit` | Executes before a story is initially visited and rendered in the browser
`async preVisit(page, context) {}` | | `postVisit` | Executes after the story is visited and fully rendered
`async postVisit(page, context) {}` | - These test hooks are experimental and may be subject to breaking changes. We encourage you to test as much as possible within the story's [play function](../../writing-stories/play-function.mdx). + +These test hooks are experimental and may be subject to breaking changes. We encourage you to test as much as possible within the story's [play function](../../writing-stories/play-function.mdx). + To enable the hooks API, you'll need to add a new configuration file inside your Storybook directory and set them up as follows: @@ -357,17 +367,19 @@ To enable the hooks API, you'll need to add a new configuration file inside your {/* prettier-ignore-end */} - Except for the `setup` function, all other functions run asynchronously. Both `preVisit` and `postVisit` functions include two additional arguments, a [Playwright page](https://playwright.dev/docs/pages) and a context object which contains the `id`, `title`, and the `name` of the story. + +Except for the `setup` function, all other functions run asynchronously. Both `preVisit` and `postVisit` functions include two additional arguments, a [Playwright page](https://playwright.dev/docs/pages) and a context object which contains the `id`, `title`, and the `name` of the story. + When the test-runner executes, your existing tests will go through the following lifecycle: -* The `setup` function is executed before all the tests run. -* The context object is generated containing the required information. -* Playwright navigates to the story's page. -* The `preVisit` function is executed. -* The story is rendered, and any existing `play` functions are executed. -* The `postVisit` function is executed. +- The `setup` function is executed before all the tests run. +- The context object is generated containing the required information. +- Playwright navigates to the story's page. +- The `preVisit` function is executed. +- The story is rendered, and any existing `play` functions are executed. +- The `postVisit` function is executed. ### (Experimental) Filter tests @@ -386,7 +398,9 @@ When you run the test-runner on Storybook, it tests every story by default. Howe {/* prettier-ignore-end */} - Running tests with the CLI flags takes precedence over the options provided in the configuration file and will override the available options in the configuration file. + +Running tests with the CLI flags takes precedence over the options provided in the configuration file and will override the available options in the configuration file. + #### Disabling tests @@ -410,7 +424,9 @@ To allow the test-runner only to run tests on a specific story or subset of stor {/* prettier-ignore-end */} - Applying tags for the component's stories should either be done at the component level (using `meta`) or at the story level. Importing tags across stories is not supported in Storybook and won't work as intended. + +Applying tags for the component's stories should either be done at the component level (using `meta`) or at the story level. Importing tags across stories is not supported in Storybook and won't work as intended. + #### Skip tests @@ -478,7 +494,9 @@ Suppose you run into a situation where the local and remote Storybooks appear ou {/* prettier-ignore-end */} - The `index.json` mode is not compatible with the watch mode. + +The `index.json` mode is not compatible with the watch mode. + If you need to disable it, use the `--no-index-json` flag: @@ -497,12 +515,12 @@ Index.json mode requires a `index.json` file. Open a browser window and navigate The test-runner is a generic testing tool that can run locally or on CI and be configured or extended to run all kinds of tests. -[Chromatic](https://www.chromatic.com/?utm_source=storybook_website\&utm_medium=link\&utm_campaign=storybook) is a cloud-based service that runs [visual](../visual-testing.mdx) and [interaction tests](../interaction-testing.mdx) (and soon [accessibility tests](../accessibility-testing.mdx)) without setting up the test runner. It also syncs with your git provider and manages access control for private projects. +[Chromatic](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook) is a cloud-based service that runs [visual](../visual-testing.mdx) and [interaction tests](../interaction-testing.mdx) (and soon [accessibility tests](../accessibility-testing.mdx)) without setting up the test runner. It also syncs with your git provider and manages access control for private projects. However, you might want to pair the test runner and Chromatic in some cases. -* Use it locally and Chromatic on your CI. -* Use Chromatic for visual and component tests and run other custom tests using the test runner. +- Use it locally and Chromatic on your CI. +- Use Chromatic for visual and component tests and run other custom tests using the test runner. ## Troubleshooting @@ -572,12 +590,12 @@ As the [coverage addon](https://storybook.js.org/addons/@storybook/addon-coverag **More testing resources** -* [Interaction testing](../interaction-testing.mdx) for user behavior simulation -* [Accessibility testing](../accessibility-testing.mdx) for accessibility -* [Visual testing](../visual-testing.mdx) for appearance -* [Snapshot testing](../snapshot-testing.mdx) for rendering errors and warnings -* [Test coverage](../test-coverage.mdx) for measuring code coverage -* [CI](../in-ci.mdx) for running tests in your CI/CD pipeline -* [Vitest addon](./vitest-addon/index.mdx) for running tests in Storybook -* [End-to-end testing](./stories-in-end-to-end-tests.mdx) for simulating real user scenarios -* [Unit testing](./stories-in-unit-tests.mdx) for functionality +- [Interaction testing](../interaction-testing.mdx) for user behavior simulation +- [Accessibility testing](../accessibility-testing.mdx) for accessibility +- [Visual testing](../visual-testing.mdx) for appearance +- [Snapshot testing](../snapshot-testing.mdx) for rendering errors and warnings +- [Test coverage](../test-coverage.mdx) for measuring code coverage +- [CI](../in-ci.mdx) for running tests in your CI/CD pipeline +- [Vitest addon](./vitest-addon/index.mdx) for running tests in Storybook +- [End-to-end testing](./stories-in-end-to-end-tests.mdx) for simulating real user scenarios +- [Unit testing](./stories-in-unit-tests.mdx) for functionality diff --git a/docs/writing-tests/integrations/vitest-addon/index.mdx b/docs/writing-tests/integrations/vitest-addon/index.mdx index 5f76c68ac4fa..48979b43c0da 100644 --- a/docs/writing-tests/integrations/vitest-addon/index.mdx +++ b/docs/writing-tests/integrations/vitest-addon/index.mdx @@ -40,13 +40,15 @@ Before installing, make sure your project meets the following requirements: - A Storybook framework that uses Vite (e.g. [`vue3-vite`](../../../get-started/frameworks/vue3-vite.mdx), [`react-vite`](../../../get-started/frameworks/react-vite.mdx), [`preact-vite`](../../../get-started/frameworks/preact-vite.mdx), [`nextjs-vite`](../../../get-started/frameworks/nextjs-vite.mdx), [`sveltekit`](../../../get-started/frameworks/sveltekit.mdx), etc.) - Vitest ≥ 3.0 - - If you're not yet using Vitest, it will be installed and configured for you automatically + - If you're not yet using Vitest, it will be installed and configured for you automatically - (optional) MSW ≥ 2.0 - - If MSW is installed, it must be v2.0.0 or later to not conflict with Vitest's dependency + - If MSW is installed, it must be v2.0.0 or later to not conflict with Vitest's dependency + **Using with Next.js** — The Vitest addon is supported in Next.js ≥ 14.1 projects, but you must be using the [`@storybook/nextjs-vite` framework](../../../get-started/frameworks/nextjs-vite.mdx). When you run the setup command below, you will be prompted to install and use the framework if you haven't already. + @@ -58,11 +60,11 @@ Run the following command to install and configure the addon automatically: The [`add` command](../../../addons/install-addons.mdx#automatic-installation) will: -* Install and register the Vitest addon -* Inspect your project's Vite and Vitest setup -* Install and configure Vitest with sensible defaults if necessary -* Set up browser mode using Playwright's Chromium browser -* **Prompt you to install Playwright browser binaries** if needed +- Install and register the Vitest addon +- Inspect your project's Vite and Vitest setup +- Install and configure Vitest with sensible defaults if necessary +- Set up browser mode using Playwright's Chromium browser +- **Prompt you to install Playwright browser binaries** if needed The setup is fully automated and handles all configuration for you. The full configuration options can be found in the [API section](#options), below. @@ -96,26 +98,26 @@ When the addon is set up automatically, it will create or adjust your Vitest con
Example Vitest config file - The most simple application of the plugin is to include it in your Vitest configuration file: +The most simple application of the plugin is to include it in your Vitest configuration file: - {/* prettier-ignore-start */} +{/* prettier-ignore-start */} - + - {/* prettier-ignore-end */} +{/* prettier-ignore-end */}
Example Vitest workspace file (Vitest < 3.2) - If you're using a [Vitest workspace](https://v3.vitest.dev/config/#workspace), you can define a new workspace project: +If you're using a [Vitest workspace](https://v3.vitest.dev/config/#workspace), you can define a new workspace project: - {/* prettier-ignore-start */} +{/* prettier-ignore-start */} - + - {/* prettier-ignore-end */} +{/* prettier-ignore-end */}
@@ -204,7 +206,7 @@ The Vitest addon works by using a Vitest plugin to transform your stories into [ Stories are tested in two ways: a smoke test to ensure it renders and, if a [play function](../../interaction-testing.mdx#writing-interaction-tests) is defined, that function is run and any [assertions made](../../interaction-testing.mdx#asserting-with-expect) within it are validated. -When you run tests in the [Storybook UI](#storybook-ui), the addon runs Vitest in the background and reports the results in the sidebar. +When you run tests in the [Storybook UI](#storybook-ui), the addon runs Vitest in the background and reports the results in the sidebar. ## Configuring tests @@ -248,12 +250,12 @@ The [test runner](../test-runner.mdx) requires a running Storybook instance to t | Feature | Vitest addon | test-runner | | -------------------------------------------------------- | ------------------ | ------------------------ | -| **Test types** | +| **Test types** | | - [Interaction tests](../../interaction-testing.mdx) | ✅ | ✅ | | - [Accessibility tests](../../accessibility-testing.mdx) | ✅ | ✅ | | - [Visual tests](../../visual-testing.mdx) | ✅ | ❌ | | - [Snapshot tests](../../snapshot-testing.mdx) | ❌ | ✅ | -| **Testing contexts** | +| **Testing contexts** | | - Storybook UI | ✅ | ❌ | | - Editor extensions | ✅ | ❌ | | - CLI | ✅ | ✅ | @@ -308,7 +310,6 @@ To isolate your Storybook tests from other tests, you need to move the `test` pr Additionally, we recommend using a [test project](#example-configuration-files) if you're using Vitest ≥ 4.0, or a workspace for previous versions to define separate configurations for your Storybook tests and other tests. This ensures each can be run either in isolation or together, depending on your needs. - ### Why do we recommend browser mode? Vitest's browser mode runs your tests in a real browser (Chromium, via Playwright, in the default configuration). The alternative is a simulated browser environment, like JSDom or HappyDom, which can have differences in behavior compared to a real browser. For UI components, which can often depend on browser APIs or features, running tests in a real browser is more accurate. @@ -329,7 +330,7 @@ By default, the export name of a story is mapped to the test name. To create a m ```js title="Example.stories.js|ts" export const Story = { - name: 'custom, descriptive name' + name: 'custom, descriptive name', }; ``` @@ -346,7 +347,7 @@ export default defineConfig({ // ... resolve: { alias: { - "@storybook/react-dom-shim": "@storybook/react-dom-shim/dist/react-16", + '@storybook/react-dom-shim': '@storybook/react-dom-shim/dist/react-16', }, }, }); @@ -411,7 +412,7 @@ See [Vitest's sharding guide](https://vitest.dev/guide/improving-performance.htm This addon has the following exports: ```js -import { storybookTest } from '@storybook/addon-vitest/vitest-plugin' +import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; ``` #### `storybookTest` @@ -492,12 +493,12 @@ You might set `disableAddonDocs` to `false` only in case your stories actually n **More testing resources** -* [Interaction testing](../../interaction-testing.mdx) for user behavior simulation -* [Accessibility testing](../../accessibility-testing.mdx) for accessibility -* [Visual testing](../../visual-testing.mdx) for appearance -* [Snapshot testing](../../snapshot-testing.mdx) for rendering errors and warnings -* [Test coverage](../../test-coverage.mdx) for measuring code coverage -* [CI](../../in-ci.mdx) for running tests in your CI/CD pipeline -* [Test runner](../test-runner.mdx) to automate test execution -* [End-to-end testing](../stories-in-end-to-end-tests.mdx) for simulating real user scenarios -* [Unit testing](../stories-in-unit-tests.mdx) for functionality +- [Interaction testing](../../interaction-testing.mdx) for user behavior simulation +- [Accessibility testing](../../accessibility-testing.mdx) for accessibility +- [Visual testing](../../visual-testing.mdx) for appearance +- [Snapshot testing](../../snapshot-testing.mdx) for rendering errors and warnings +- [Test coverage](../../test-coverage.mdx) for measuring code coverage +- [CI](../../in-ci.mdx) for running tests in your CI/CD pipeline +- [Test runner](../test-runner.mdx) to automate test execution +- [End-to-end testing](../stories-in-end-to-end-tests.mdx) for simulating real user scenarios +- [Unit testing](../stories-in-unit-tests.mdx) for functionality diff --git a/docs/writing-tests/integrations/vitest-addon/migration-guide.mdx b/docs/writing-tests/integrations/vitest-addon/migration-guide.mdx index af8c68b5707f..551553574bfc 100644 --- a/docs/writing-tests/integrations/vitest-addon/migration-guide.mdx +++ b/docs/writing-tests/integrations/vitest-addon/migration-guide.mdx @@ -57,9 +57,9 @@ Remove the following files: ### 3. Set up `@storybook/addon-vitest` - Run this command, which will set up all the necessary code for you. - + - + - Update the `package.json` script for Storybook tests, replacing the test-runner binary and using Vitest instead: ```diff title="package.json" @@ -69,6 +69,7 @@ Remove the following files: + "test-storybook": "vitest --project=storybook" } } + ``` ### 4. Update your CI to run Vitest instead of the test-runner @@ -127,4 +128,4 @@ You can still migrate to use Vitest, but if you believe you won't benefit from i There are solutions for image snapshot testing that integrate with the Storybook testing widget: - On the cloud with the [Visual tests addon](../../visual-testing.mdx) -- Locally with [`storybook-addon-vis`](https://github.com/repobuddy/visual-testing) \ No newline at end of file +- Locally with [`storybook-addon-vis`](https://github.com/repobuddy/visual-testing) diff --git a/docs/writing-tests/interaction-testing.mdx b/docs/writing-tests/interaction-testing.mdx index cbbb4b535f38..f7407705d817 100644 --- a/docs/writing-tests/interaction-testing.mdx +++ b/docs/writing-tests/interaction-testing.mdx @@ -87,6 +87,7 @@ Now, those queries are available on the `canvas` object in your `play` function: {/* END only web-components */} + ### Simulating behavior with `userEvent` @@ -99,8 +100,8 @@ There are many methods available on `userEvent`, which are detailed in the [`use | ----------------- | ------------------------------------------------------------------------------------------------------------------------ | | `click` | Clicks the element, calling a click() function
`await userEvent.click()` | | `dblClick` | Clicks the element twice
`await userEvent.dblClick()` | -| `hover` | Hovers an element
`await userEvent.hover()` | -| `unhover` | Unhovers out of element
`await userEvent.unhover()` | +| `hover` | Hovers an element
`await userEvent.hover()` | +| `unhover` | Unhovers out of element
`await userEvent.unhover()` | | `tab` | Presses the tab key
`await userEvent.tab()` | | `type` | Writes text inside inputs or textareas
`await userEvent.type(, 'Some text');` | | `keyboard` | Simulates keyboard events
`await userEvent.keyboard('{Shift}');` | @@ -110,7 +111,7 @@ There are many methods available on `userEvent`, which are detailed in the [`use -`userEvent` methods should *always* be `await`ed inside of the play function. This ensures they can be properly logged and debugged in the Interactions panel. +`userEvent` methods should _always_ be `await`ed inside of the play function. This ensures they can be properly logged and debugged in the Interactions panel. @@ -128,13 +129,13 @@ The `expect` utility here combines the methods available in [Vitest’s `expect` | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | [`toBeInTheDocument()`](https://github.com/testing-library/jest-dom#tobeinthedocument) | Checks if the element is in the DOM
`await expect().toBeInTheDocument()` | | [`toBeVisible()`](https://github.com/testing-library/jest-dom#tobevisible) | Checks if the element is visible to the user
`await expect().toBeVisible()` | -| [`toHaveAttribute()`](https://github.com/testing-library/jest-dom#tohaveattribute) | Checks if an element has an attribute
`await expect().toHaveAttribute('aria-disabled', 'true')` | +| [`toHaveAttribute()`](https://github.com/testing-library/jest-dom#tohaveattribute) | Checks if an element has an attribute
`await expect().toHaveAttribute('aria-disabled', 'true')` | | [`toHaveBeenCalled()`](https://vitest.dev/api/expect.html#tohavebeencalled) | Checks that a spied function was called
`await expect().toHaveBeenCalled()` | | [`toHaveBeenCalledWith()`](https://vitest.dev/api/expect.html#tohavebeencalledwith) | Checks that a spied function was called with specific parameters
`await expect().toHaveBeenCalledWith('example')` | -`expect` calls should *always* be `await`ed inside of the play function. This ensures they can be properly logged and debugged in the Interactions panel. +`expect` calls should _always_ be `await`ed inside of the play function. This ensures they can be properly logged and debugged in the Interactions panel. @@ -143,7 +144,7 @@ The `expect` utility here combines the methods available in [Vitest’s `expect` When your component calls a function, you can spy on that function to make assertions on its behavior using the `fn` utility from Vitest, available via the `storybook/test` module: ```js -import { fn } from 'storybook/test' +import { fn } from 'storybook/test'; ``` Most of the time, you will use `fn` as an `arg` value when writing your story, then access that `arg` in your test: @@ -164,7 +165,7 @@ Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate There are two requirements to use the `mount` function: -1. You *must* destructure the mount property from the `context` (the argument passed to your play function). This makes sure that Storybook does not start rendering the story before the play function begins. +1. You _must_ destructure the mount property from the `context` (the argument passed to your play function). This makes sure that Storybook does not start rendering the story before the play function begins. 2. Your Storybook framework or builder must be configured to transpile to ES2017 or newer. This is because destructuring statements and async/await usages are otherwise transpiled away, which prevents Storybook from recognizing your usage of `mount`. @@ -323,12 +324,12 @@ Interaction tests integrate Vitest and Testing Library into Storybook. The bigge **More testing resources** -* [Vitest addon](./integrations/vitest-addon/index.mdx) for running tests in Storybook -* [Accessibility testing](./accessibility-testing.mdx) for accessibility -* [Visual testing](./visual-testing.mdx) for appearance -* [Snapshot testing](./snapshot-testing.mdx) for rendering errors and warnings -* [Test coverage](./test-coverage.mdx) for measuring code coverage -* [CI](./in-ci.mdx) for running tests in your CI/CD pipeline -* [End-to-end testing](./integrations/stories-in-end-to-end-tests.mdx) for simulating real user scenarios -* [Unit testing](./integrations/stories-in-unit-tests.mdx) for functionality -* [Test runner](./integrations/test-runner.mdx) to automate test execution +- [Vitest addon](./integrations/vitest-addon/index.mdx) for running tests in Storybook +- [Accessibility testing](./accessibility-testing.mdx) for accessibility +- [Visual testing](./visual-testing.mdx) for appearance +- [Snapshot testing](./snapshot-testing.mdx) for rendering errors and warnings +- [Test coverage](./test-coverage.mdx) for measuring code coverage +- [CI](./in-ci.mdx) for running tests in your CI/CD pipeline +- [End-to-end testing](./integrations/stories-in-end-to-end-tests.mdx) for simulating real user scenarios +- [Unit testing](./integrations/stories-in-unit-tests.mdx) for functionality +- [Test runner](./integrations/test-runner.mdx) to automate test execution diff --git a/docs/writing-tests/snapshot-testing.mdx b/docs/writing-tests/snapshot-testing.mdx index fda8c1b1f5ca..d8c51b3b91d9 100644 --- a/docs/writing-tests/snapshot-testing.mdx +++ b/docs/writing-tests/snapshot-testing.mdx @@ -78,10 +78,10 @@ In this example, we have a simple Button React component which for some reason a ```tsx title="Button.tsx" function Button(props) { if (props.doNotUseThisItWillThrowAnError) { - throw new Error("I tried to tell you...") + throw new Error('I tried to tell you...'); } - return + ); } diff --git a/test-storybooks/portable-stories-kitchen-sink/nextjs/styles/Home.module.css b/test-storybooks/portable-stories-kitchen-sink/nextjs/styles/Home.module.css index eee920e64c6e..1a66de0f2737 100644 --- a/test-storybooks/portable-stories-kitchen-sink/nextjs/styles/Home.module.css +++ b/test-storybooks/portable-stories-kitchen-sink/nextjs/styles/Home.module.css @@ -51,7 +51,9 @@ border-radius: var(--border-radius); background: rgba(var(--card-rgb), 0); border: 1px solid rgba(var(--card-border-rgb), 0); - transition: background 200ms, border 200ms; + transition: + background 200ms, + border 200ms; } .card span { @@ -97,7 +99,7 @@ .center::before, .center::after { - content: ""; + content: ''; left: 50%; position: absolute; filter: blur(45px); @@ -193,11 +195,7 @@ inset: auto 0 0; padding: 2rem; height: 200px; - background: linear-gradient( - to bottom, - transparent 0%, - rgb(var(--background-end-rgb)) 40% - ); + background: linear-gradient(to bottom, transparent 0%, rgb(var(--background-end-rgb)) 40%); z-index: 1; } } diff --git a/test-storybooks/portable-stories-kitchen-sink/nextjs/styles/globals.css b/test-storybooks/portable-stories-kitchen-sink/nextjs/styles/globals.css index f4bd77c0ccac..6124b37e8930 100644 --- a/test-storybooks/portable-stories-kitchen-sink/nextjs/styles/globals.css +++ b/test-storybooks/portable-stories-kitchen-sink/nextjs/styles/globals.css @@ -1,9 +1,9 @@ :root { --max-width: 1100px; --border-radius: 12px; - --font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", - "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", - "Fira Mono", "Droid Sans Mono", "Courier New", monospace; + --font-mono: + ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', 'Roboto Mono', 'Oxygen Mono', + 'Ubuntu Monospace', 'Source Code Pro', 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; --foreground-rgb: 0, 0, 0; --background-start-rgb: 214, 219, 220; @@ -17,10 +17,7 @@ #0071ff33 160deg, transparent 360deg ); - --secondary-glow: radial-gradient( - rgba(255, 255, 255, 1), - rgba(255, 255, 255, 0) - ); + --secondary-glow: radial-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 0)); --tile-start-rgb: 239, 245, 249; --tile-end-rgb: 228, 232, 233; @@ -87,11 +84,7 @@ body { body { color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) + background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb)); } diff --git a/test-storybooks/portable-stories-kitchen-sink/nextjs/typings.d.ts b/test-storybooks/portable-stories-kitchen-sink/nextjs/typings.d.ts index 92402fdf0035..def7f0d8fe14 100644 --- a/test-storybooks/portable-stories-kitchen-sink/nextjs/typings.d.ts +++ b/test-storybooks/portable-stories-kitchen-sink/nextjs/typings.d.ts @@ -1,3 +1,3 @@ -declare module '*.png' -declare module '*.svg' -declare module '*.avif' \ No newline at end of file +declare module '*.png'; +declare module '*.svg'; +declare module '*.avif'; diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.eslintrc.cjs b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.eslintrc.cjs index 29cb6d5a0877..f51e2baf0b82 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.eslintrc.cjs +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.eslintrc.cjs @@ -1,14 +1,16 @@ module.exports = { root: true, env: { browser: true, es2020: true }, - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + 'plugin:storybook/recommended', + ], ignorePatterns: ['dist', '.eslintrc.cjs'], parser: '@typescript-eslint/parser', plugins: ['react-refresh'], rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], }, -} +}; diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/get-decorator-string.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/get-decorator-string.ts index 794f14db5674..44efef9ba0ee 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/get-decorator-string.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/get-decorator-string.ts @@ -1,3 +1,3 @@ export const getDecoratorString = () => { - return "Global Decorator"; + return 'Global Decorator'; }; diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/main.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/main.ts index fa0ee95488bf..969ee138815e 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/main.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/main.ts @@ -1,24 +1,24 @@ -import { join } from "node:path"; +import { join } from 'node:path'; -import type { StorybookConfig } from "@storybook/react-vite"; +import type { StorybookConfig } from '@storybook/react-vite'; const config: StorybookConfig = { - stories: ["../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)"], - addons: ["@storybook/addon-vitest", "@storybook/addon-a11y"], + stories: ['../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + addons: ['@storybook/addon-vitest', '@storybook/addon-a11y'], framework: { - name: "@storybook/react-vite", + name: '@storybook/react-vite', options: {}, }, core: { disableWhatsNewNotifications: true, }, - previewHead: (head = "") => `${head} + previewHead: (head = '') => `${head} `, - staticDirs: [{ from: "./test-static-dirs", to: "test-static-dirs" }], + staticDirs: [{ from: './test-static-dirs', to: 'test-static-dirs' }], viteFinal: (config) => { return { ...config, @@ -30,19 +30,19 @@ const config: StorybookConfig = { ...config.resolve, alias: { ...config.resolve?.alias, - "test-alias": join(import.meta.dirname, "aliased.ts"), + 'test-alias': join(import.meta.dirname, 'aliased.ts'), }, }, }; }, refs: { - "storybook@8.0.0": { - title: "Storybook 8.0.0", - url: "https://635781f3500dd2c49e189caf-gckybvsekn.chromatic.com/", + 'storybook@8.0.0': { + title: 'Storybook 8.0.0', + url: 'https://635781f3500dd2c49e189caf-gckybvsekn.chromatic.com/', }, - "storybook@7.6.18": { - title: "Storybook 7.6.18", - url: "https://635781f3500dd2c49e189caf-oljwjdrftz.chromatic.com/", + 'storybook@7.6.18': { + title: 'Storybook 7.6.18', + url: 'https://635781f3500dd2c49e189caf-oljwjdrftz.chromatic.com/', }, }, }; diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/setup-file-dependency.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/setup-file-dependency.ts index 592e8f52b66b..d433f58cf120 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/setup-file-dependency.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/setup-file-dependency.ts @@ -1,3 +1,3 @@ export const getString = () => { - return "initial string"; + return 'initial string'; }; diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts index b91b98d3edf7..e7d45a393228 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts @@ -1,40 +1,30 @@ -import { promises as fs } from "node:fs"; -import path from "node:path"; +import { promises as fs } from 'node:fs'; +import path from 'node:path'; -import { expect, test } from "@playwright/test"; +import { expect, test } from '@playwright/test'; -import { SbPage } from "../../../../code/e2e-tests/util"; +import { SbPage } from '../../../../code/e2e-tests/util'; -const STORYBOOK_URL = "http://localhost:6006"; -const TEST_STORY_PATH = path.resolve( - __dirname, - "..", - "stories", - "AddonTest.stories.tsx" -); +const STORYBOOK_URL = 'http://localhost:6006'; +const TEST_STORY_PATH = path.resolve(__dirname, '..', 'stories', 'AddonTest.stories.tsx'); const UNHANDLED_ERRORS_STORY_PATH = path.resolve( __dirname, - "..", - "stories", - "UnhandledErrors.stories.tsx" -); -const ADDON_TEST_DEPENDENCY_PATH = path.resolve( - __dirname, - "..", - "stories", - "get-button-string.ts" + '..', + 'stories', + 'UnhandledErrors.stories.tsx' ); +const ADDON_TEST_DEPENDENCY_PATH = path.resolve(__dirname, '..', 'stories', 'get-button-string.ts'); const PREVIEW_DEPENDENCY_PATH = path.resolve( __dirname, - "..", - ".storybook", - "get-decorator-string.ts" + '..', + '.storybook', + 'get-decorator-string.ts' ); const SETUP_FILE_DEPENDENCY_PATH = path.resolve( __dirname, - "..", - ".storybook", - "setup-file-dependency.ts" + '..', + '.storybook', + 'setup-file-dependency.ts' ); const setForceFailureFlag = (content: string, value: boolean) => @@ -42,10 +32,7 @@ const setForceFailureFlag = (content: string, value: boolean) => const modifiedFiles = new Map(); -const modifyFile = async ( - filePath: string, - modify: (content: string) => string -) => { +const modifyFile = async (filePath: string, modify: (content: string) => string) => { const content = (await fs.readFile(filePath)).toString(); const modifiedContent = modify(content); await fs.writeFile(filePath, modifiedContent); @@ -66,8 +53,8 @@ const restoreAllFiles = async () => { await new Promise((resolve) => setTimeout(resolve, 2000)); }; -test.describe("component testing", () => { - test.describe.configure({ mode: "serial" }); +test.describe('component testing', () => { + test.describe.configure({ mode: 'serial' }); test.beforeEach(async ({ page }) => { const sbPage = new SbPage(page, expect); @@ -75,28 +62,24 @@ test.describe("component testing", () => { await page.evaluate(() => window.sessionStorage.clear()); await sbPage.waitUntilLoaded(); - const expandTestingModule = page.getByLabel("Expand testing module"); + const expandTestingModule = page.getByLabel('Expand testing module'); if (await expandTestingModule.isVisible()) { await expandTestingModule.click(); } }); test.afterEach(async ({ page }) => { - await page.click("body"); + await page.click('body'); try { /** Sometimes the vitest instance fails, but the error-content is only shown in a modal. * We click the link so the modal opens and we can close it. * Having it open shortly is enough to have it be in the playwright trace, for debugging purposes. */ - const descriptionButton = page.locator("#testing-module-description button"); - if ( - await descriptionButton.isVisible({ timeout: 4000 }).catch(() => false) - ) { + const descriptionButton = page.locator('#testing-module-description button'); + if (await descriptionButton.isVisible({ timeout: 4000 }).catch(() => false)) { await descriptionButton.click({ timeout: 4000, force: true }); - await page - .getByLabel("Close modal") - .click({ timeout: 4000, force: true }); + await page.getByLabel('Close modal').click({ timeout: 4000, force: true }); } } catch { // Ignore any errors when trying to open the modal @@ -104,31 +87,31 @@ test.describe("component testing", () => { await restoreAllFiles(); - const expandTestingModule = page.getByLabel("Expand testing module"); + const expandTestingModule = page.getByLabel('Expand testing module'); if (await expandTestingModule.isVisible()) { await expandTestingModule.click(); } // Make sure any popover is closed - await page.click("body"); + await page.click('body'); // Ensure that all test results are removed and features are disabled, as previous tests might have enabled them - const clearStatusesButton = page.getByLabel("Clear all statuses"); + const clearStatusesButton = page.getByLabel('Clear all statuses'); if (await clearStatusesButton.isVisible()) { await clearStatusesButton.click(); } - const watchModeToggle = page.getByRole("switch", { name: "Watch mode" }); + const watchModeToggle = page.getByRole('switch', { name: 'Watch mode' }); if ( (await watchModeToggle.isVisible()) && - (await watchModeToggle.getAttribute("aria-checked")) === "true" + (await watchModeToggle.getAttribute('aria-checked')) === 'true' ) { await watchModeToggle.click(); } const configs = [ - page.getByRole("checkbox", { name: "Coverage" }), - page.getByRole("checkbox", { name: "Accessibility" }), + page.getByRole('checkbox', { name: 'Coverage' }), + page.getByRole('checkbox', { name: 'Accessibility' }), ]; for (const config of configs) { if (await config.isChecked()) { @@ -137,38 +120,31 @@ test.describe("component testing", () => { } }); - test("should show discrepancy between test results", async ({ - page, - browserName, - }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test('should show discrepancy between test results', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); test.setTimeout(40_000); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Mismatch Failure"); + await sbPage.navigateToStory('addons/group/test', 'Mismatch Failure'); // For whatever reason, sometimes it takes longer for the story to load - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); - await sbPage.viewAddonPanel("Interactions"); + await sbPage.viewAddonPanel('Interactions'); // For whatever reason, when visiting a story sometimes the story element is collapsed and that causes flake - const testStoryElement = await page.getByRole("button", { - name: "Test", + const testStoryElement = await page.getByRole('button', { + name: 'Test', exact: true, }); - if ((await testStoryElement.getAttribute("aria-expanded")) !== "true") { + if ((await testStoryElement.getAttribute('aria-expanded')) !== 'true') { testStoryElement.click(); } - const testingModuleDescription = await page.locator( - "#testing-module-description" - ); + const testingModuleDescription = await page.locator('#testing-module-description'); - const runTestsButton = await page.getByLabel("Start test run"); + const runTestsButton = await page.getByLabel('Start test run'); await runTestsButton.click(); await expect(testingModuleDescription).not.toContainText(/Ran \d+ tests/, { @@ -180,101 +156,75 @@ test.describe("component testing", () => { timeout: 60000, }); - const errorFilter = page.getByLabel( - /Filter main navigation to show \d+ tests with errors/ - ); + const errorFilter = page.getByLabel(/Filter main navigation to show \d+ tests with errors/); await expect(errorFilter).toBeVisible(); // Assert discrepancy: CLI pass + Browser fail const failingStoryElement = page.locator( '[data-item-id="addons-group-test--mismatch-failure"] [data-testid="tree-status-button"]' ); - await expect(failingStoryElement).toHaveAttribute( - "aria-label", - "Test status: success" - ); + await expect(failingStoryElement).toHaveAttribute('aria-label', 'Test status: success'); await expect(sbPage.panelContent()).toContainText( /This interaction test passed in the CLI, but the tests failed in this browser/ ); // Assert discrepancy: CLI fail + Browser pass - await sbPage.navigateToStory("addons/group/test", "Mismatch Success"); + await sbPage.navigateToStory('addons/group/test', 'Mismatch Success'); const successfulStoryElement = page.locator( '[data-item-id="addons-group-test--mismatch-success"] [data-testid="tree-status-button"]' ); - await expect(successfulStoryElement).toHaveAttribute( - "aria-label", - "Test status: error" - ); + await expect(successfulStoryElement).toHaveAttribute('aria-label', 'Test status: error'); await expect(sbPage.panelContent()).toContainText( /This interaction test passed in this browser, but the tests failed in the CLI/ ); }); - test("should execute tests via testing module UI", async ({ - page, - browserName, - }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, true) - ); + test('should execute tests via testing module UI', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, true)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); // For whatever reason, sometimes it takes longer for the story to load - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); - await expect(page.locator("#testing-module-title")).toHaveText( - "Run component tests" - ); + await expect(page.locator('#testing-module-title')).toHaveText('Run component tests'); - const runTestsButton = await page.getByLabel("Start test run"); - const watchModeButton = await page.getByRole("switch", { - name: "Watch mode", + const runTestsButton = await page.getByLabel('Start test run'); + const watchModeButton = await page.getByRole('switch', { + name: 'Watch mode', }); - await expect(runTestsButton).not.toHaveAttribute("aria-disabled", "true"); - await expect(watchModeButton).not.toHaveAttribute("aria-disabled", "true"); + await expect(runTestsButton).not.toHaveAttribute('aria-disabled', 'true'); + await expect(watchModeButton).not.toHaveAttribute('aria-disabled', 'true'); await runTestsButton.click(); - await expect(watchModeButton).toHaveAttribute("aria-disabled", "true"); + await expect(watchModeButton).toHaveAttribute('aria-disabled', 'true'); // Wait for test results to appear - await expect(page.locator("#testing-module-description")).toHaveText( - /Ran \d+ tests/, - { timeout: 30000 } - ); + await expect(page.locator('#testing-module-description')).toHaveText(/Ran \d+ tests/, { + timeout: 30000, + }); - await expect(runTestsButton).not.toHaveAttribute("aria-disabled", "true"); - await expect(watchModeButton).not.toHaveAttribute("aria-disabled", "true"); + await expect(runTestsButton).not.toHaveAttribute('aria-disabled', 'true'); + await expect(watchModeButton).not.toHaveAttribute('aria-disabled', 'true'); - const errorFilter = page.getByLabel( - /Filter main navigation to show \d+ tests with errors/ - ); + const errorFilter = page.getByLabel(/Filter main navigation to show \d+ tests with errors/); await expect(errorFilter).toBeVisible(); // Assert for expected success const successfulStoryElement = page.locator( '[data-item-id="addons-group-test--expected-success"] [data-testid="tree-status-button"]' ); - await expect(successfulStoryElement).toHaveAttribute( - "aria-label", - "Test status: success" - ); + await expect(successfulStoryElement).toHaveAttribute('aria-label', 'Test status: success'); // Assert for expected failure const failingStoryElement = page.locator( '[data-item-id="addons-group-test--expected-failure"] [data-testid="tree-status-button"]' ); - await expect(failingStoryElement).toHaveAttribute( - "aria-label", - "Test status: error" - ); + await expect(failingStoryElement).toHaveAttribute('aria-label', 'Test status: error'); // Assert that filter works as intended await errorFilter.click(); @@ -285,55 +235,41 @@ test.describe("component testing", () => { await expect(sidebarItems).toHaveCount(2); }); - test("should run tests in watch mode when a story file is changed", async ({ + test('should run tests in watch mode when a story file is changed', async ({ page, browserName, }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, false) - ); + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, false)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); // For whatever reason, sometimes it takes longer for the story to load - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); - await page.getByRole("switch", { name: "Watch mode" }).click(); + await page.getByRole('switch', { name: 'Watch mode' }).click(); // We shouldn't have to do an arbitrary wait, but because there is no UI for loading state yet, we have to await page.waitForTimeout(8000); - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, true) - ); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, true)); // Wait for test results to appear - const errorFilter = page.getByLabel( - /Filter main navigation to show \d+ tests with errors/ - ); + const errorFilter = page.getByLabel(/Filter main navigation to show \d+ tests with errors/); await expect(errorFilter).toBeVisible({ timeout: 30000 }); // Assert for expected success const successfulStoryElement = page.locator( '[data-item-id="addons-group-test--expected-success"] [data-testid="tree-status-button"]' ); - await expect(successfulStoryElement).toHaveAttribute( - "aria-label", - "Test status: success" - ); + await expect(successfulStoryElement).toHaveAttribute('aria-label', 'Test status: success'); // Assert for expected failure const failingStoryElement = page.locator( '[data-item-id="addons-group-test--expected-failure"] [data-testid="tree-status-button"]' ); - await expect(failingStoryElement).toHaveAttribute( - "aria-label", - "Test status: error" - ); + await expect(failingStoryElement).toHaveAttribute('aria-label', 'Test status: error'); // Assert that filter works as intended await errorFilter.click(); @@ -348,211 +284,171 @@ test.describe("component testing", () => { page, browserName, }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); // For whatever reason, sometimes it takes longer for the story to load - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); - await page.getByRole("switch", { name: "Watch mode" }).click(); + await page.getByRole('switch', { name: 'Watch mode' }).click(); // We shouldn't have to do an arbitrary wait, but because there is no UI for loading state yet, we have to await page.waitForTimeout(3000); - await modifyFile(ADDON_TEST_DEPENDENCY_PATH, (content) => - content.replace("test", "changed") - ); + await modifyFile(ADDON_TEST_DEPENDENCY_PATH, (content) => content.replace('test', 'changed')); // Expect less than 10 tests to have run - await expect(page.locator("#testing-module-description")).toContainText( - /Ran \d tests/, - { timeout: 30000 } - ); + await expect(page.locator('#testing-module-description')).toContainText(/Ran \d tests/, { + timeout: 30000, + }); // Assert for expected failure const failingStoryElement = page.locator( '[data-item-id="addons-group-test--expected-content"] [data-testid="tree-status-button"]' ); - await expect(failingStoryElement).toHaveAttribute( - "aria-label", - "Test status: error" - ); + await expect(failingStoryElement).toHaveAttribute('aria-label', 'Test status: error'); }); test("should run all tests in watch mode when the preview file's dependency is changed", async ({ page, browserName, }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); // For whatever reason, sometimes it takes longer for the story to load - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); - await page.getByRole("switch", { name: "Watch mode" }).click(); + await page.getByRole('switch', { name: 'Watch mode' }).click(); // We shouldn't have to do an arbitrary wait, but because there is no UI for loading state yet, we have to await page.waitForTimeout(3000); await modifyFile(PREVIEW_DEPENDENCY_PATH, (content) => - content.replace("Global Decorator", "Changed Decorator") + content.replace('Global Decorator', 'Changed Decorator') ); // Expect at least 20 tests to have run - await expect(page.locator("#testing-module-description")).toContainText( - /Ran [2-9]\d tests/, - { timeout: 30000 } - ); + await expect(page.locator('#testing-module-description')).toContainText(/Ran [2-9]\d tests/, { + timeout: 30000, + }); // Assert for expected failure const failingStoryElement = page.locator( '[data-item-id="addons-group-test--expected-content"] [data-testid="tree-status-button"]' ); - await expect(failingStoryElement).toHaveAttribute( - "aria-label", - "Test status: error" - ); + await expect(failingStoryElement).toHaveAttribute('aria-label', 'Test status: error'); }); test("should run all tests in watch mode when the setup file's dependency is changed", async ({ page, browserName, }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); // For whatever reason, sometimes it takes longer for the story to load - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); - await page.getByRole("switch", { name: "Watch mode" }).click(); + await page.getByRole('switch', { name: 'Watch mode' }).click(); // We shouldn't have to do an arbitrary wait, but because there is no UI for loading state yet, we have to await page.waitForTimeout(3000); await modifyFile(SETUP_FILE_DEPENDENCY_PATH, (content) => - content.replace("initial string", "changed string") + content.replace('initial string', 'changed string') ); // Expect at least 20 tests to have run - await expect(page.locator("#testing-module-description")).toContainText( - /Ran [2-9]\d tests/, - { timeout: 30000 } - ); + await expect(page.locator('#testing-module-description')).toContainText(/Ran [2-9]\d tests/, { + timeout: 30000, + }); }); - test("should collect coverage to testing module and HTML report", async ({ + test('should collect coverage to testing module and HTML report', async ({ page, browserName, }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); // Arrange - Prepare Storybook - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, false) - ); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, false)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); // Assert - No coverage report initially - await expect(page.getByLabel("Open coverage report")).toHaveCount(0); + await expect(page.getByLabel('Open coverage report')).toHaveCount(0); // Act - Enable coverage and run tests - await page.getByLabel("Coverage", { exact: true }).click(); + await page.getByLabel('Coverage', { exact: true }).click(); // Wait for Vitest to have (re)started await page.waitForTimeout(2000); - await page.getByLabel("Start test run").click(); + await page.getByLabel('Start test run').click(); // Assert - Coverage report is collected and shown - await expect(page.getByLabel("Open coverage report")).toBeVisible({ + await expect(page.getByLabel('Open coverage report')).toBeVisible({ timeout: 30000, }); - const sbPercentageText = await page - .getByLabel(/% coverage\)$/) - .textContent(); + const sbPercentageText = await page.getByLabel(/% coverage\)$/).textContent(); expect(sbPercentageText).toMatch(/^\d+%$/); - const sbPercentage = Number.parseInt( - sbPercentageText!.replace("%", "") ?? "" - ); + const sbPercentage = Number.parseInt(sbPercentageText!.replace('%', '') ?? ''); expect(sbPercentage).toBeGreaterThanOrEqual(0); expect(sbPercentage).toBeLessThanOrEqual(100); // Act - Open HTML coverage report - const coverageReportLink = await page.getByLabel("Open coverage report"); + const coverageReportLink = await page.getByLabel('Open coverage report'); // Remove target="_blank" attribute to open in the same tab - await coverageReportLink.evaluate((elem) => elem.removeAttribute("target")); - await page.getByLabel("Open coverage report").click(); + await coverageReportLink.evaluate((elem) => elem.removeAttribute('target')); + await page.getByLabel('Open coverage report').click(); // Assert - HTML coverage report is accessible and reports the same coverage percentage as Storybook const htmlPercentageText = - (await page - .locator('span:has(+ :text("Statements"))') - .first() - .textContent()) ?? ""; - const htmlPercentage = Number.parseFloat( - htmlPercentageText.replace("% ", "") - ); + (await page.locator('span:has(+ :text("Statements"))').first().textContent()) ?? ''; + const htmlPercentage = Number.parseFloat(htmlPercentageText.replace('% ', '')); expect(Math.round(htmlPercentage)).toBe(sbPercentage); await page.goBack(); }); - test("should run focused test for a single story", async ({ - page, - browserName, - }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test('should run focused test for a single story', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); // Arrange - Prepare Storybook - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, false) - ); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, false)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); // Act - Open sidebar context menu, start focused test then close menu - await page - .locator('[data-item-id="addons-group-test--expected-failure"]') - .hover(); + await page.locator('[data-item-id="addons-group-test--expected-failure"]').hover(); await page .locator( '[data-item-id="addons-group-test--expected-failure"] button[data-testid="context-menu"]' ) .click(); - const sidebarContextMenu = page.getByRole("dialog"); - await sidebarContextMenu.getByLabel("Start test run").click(); + const sidebarContextMenu = page.getByRole('dialog'); + await sidebarContextMenu.getByLabel('Start test run').click(); // Assert - Only one test is running and reported - await expect( - sidebarContextMenu.locator("#testing-module-description") - ).toContainText("Ran 1 test", { timeout: 30000 }); - await expect( - sidebarContextMenu.getByLabel("Component tests passed") - ).toHaveCount(1); - await page.click("body"); + await expect(sidebarContextMenu.locator('#testing-module-description')).toContainText( + 'Ran 1 test', + { timeout: 30000 } + ); + await expect(sidebarContextMenu.getByLabel('Component tests passed')).toHaveCount(1); + await page.click('body'); await expect( page.locator( '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: success"]' @@ -560,112 +456,89 @@ test.describe("component testing", () => { ).toHaveCount(1); }); - test("should show unhandled errors in the testing module", async ({ - page, - browserName, - }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test('should show unhandled errors in the testing module', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); // Arrange - Prepare Storybook - await modifyFile(UNHANDLED_ERRORS_STORY_PATH, (content) => - setForceFailureFlag(content, true) - ); + await modifyFile(UNHANDLED_ERRORS_STORY_PATH, (content) => setForceFailureFlag(content, true)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("example/unhandlederrors", "Success"); + await sbPage.navigateToStory('example/unhandlederrors', 'Success'); - const storyElement = sbPage.getCanvasBodyElement().getByText("Hello world"); + const storyElement = sbPage.getCanvasBodyElement().getByText('Hello world'); await expect(storyElement).toBeVisible({ timeout: 30000 }); // Act - Open sidebar context menu and start focused test await page.locator('[data-item-id="example-unhandlederrors"]').hover(); await page - .locator( - '[data-item-id="example-unhandlederrors"] button[data-testid="context-menu"]' - ) + .locator('[data-item-id="example-unhandlederrors"] button[data-testid="context-menu"]') .click(); - const sidebarContextMenu = page.getByRole("dialog"); - await sidebarContextMenu.getByLabel("Start test run").click(); + const sidebarContextMenu = page.getByRole('dialog'); + await sidebarContextMenu.getByLabel('Start test run').click(); // HACK: the testing module popover has poor tracking of focus due to how many disabled // buttons it has and how deeply it changes its UI on events. This would be solved once // we move to a declarative menu, and there's an ongoing PR for that. Until then, we tab // around to reset focus. - await page.keyboard.press("Tab"); - await page.keyboard.press("Escape"); - await page.click("body"); + await page.keyboard.press('Tab'); + await page.keyboard.press('Escape'); + await page.click('body'); await expect(sidebarContextMenu).not.toBeVisible(); // Assert - Tests are running and errors are reported - const errorLink = page.locator( - "#storybook-testing-module #testing-module-description button" - ); - await expect(errorLink).toContainText("View full error", { + const errorLink = page.locator('#storybook-testing-module #testing-module-description button'); + await expect(errorLink).toContainText('View full error', { timeout: 30000, }); await errorLink.click(); - await expect(page.locator("pre")).toContainText( - "I THREW AN UNHANDLED ERROR!" - ); - await expect(page.locator("pre")).toContainText("This error originated in"); - await expect(page.locator("pre")).toContainText( + await expect(page.locator('pre')).toContainText('I THREW AN UNHANDLED ERROR!'); + await expect(page.locator('pre')).toContainText('This error originated in'); + await expect(page.locator('pre')).toContainText( "The latest test that might've caused the error is" ); - await page.getByLabel("Close modal").click(); + await page.getByLabel('Close modal').click(); }); - test("should run focused test for a component", async ({ - page, - browserName, - }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test('should run focused test for a component', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); // Arrange - Prepare Storybook - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, false) - ); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, false)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); // Act - Open sidebar context menu and start focused test await page.locator('[data-item-id="addons-group-test"]').hover(); await page - .locator( - '[data-item-id="addons-group-test"] button[data-testid="context-menu"]' - ) + .locator('[data-item-id="addons-group-test"] button[data-testid="context-menu"]') .click(); - const sidebarContextMenu = page.getByRole("dialog"); - await sidebarContextMenu.getByLabel("Start test run").click(); + const sidebarContextMenu = page.getByRole('dialog'); + await sidebarContextMenu.getByLabel('Start test run').click(); // Assert - Tests are running and reported - await expect( - sidebarContextMenu.locator("#testing-module-description") - ).toContainText("Ran 9 tests", { timeout: 30000 }); + await expect(sidebarContextMenu.locator('#testing-module-description')).toContainText( + 'Ran 9 tests', + { timeout: 30000 } + ); // Assert - Failing test shows as a failed status + await expect(sidebarContextMenu.getByLabel('Component tests failed')).toHaveCount(1); await expect( - sidebarContextMenu.getByLabel("Component tests failed") - ).toHaveCount(1); - await expect( - sidebarContextMenu.getByLabel( - "Component tests failed (1 errors or warnings so far)" - ) + sidebarContextMenu.getByLabel('Component tests failed (1 errors or warnings so far)') ).toBeVisible(); // HACK: the testing module popover has poor tracking of focus due to how many disabled // buttons it has and how deeply it changes its UI on events. This would be solved once // we move to a declarative menu, and there's an ongoing PR for that. Until then, we tab // around to reset focus. - await page.keyboard.press("Tab"); - await page.keyboard.press("Escape"); - await page.click("body"); + await page.keyboard.press('Tab'); + await page.keyboard.press('Escape'); + await page.click('body'); await expect(sidebarContextMenu).not.toBeVisible(); - await page.click("body"); + await page.click('body'); await expect( page.locator( '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: success"]' @@ -678,55 +551,42 @@ test.describe("component testing", () => { ).toHaveCount(3); }); - test("should run focused test for a group", async ({ page, browserName }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test('should run focused test for a group', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); // Arrange - Prepare Storybook - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, false) - ); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, false)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); // Act - Open sidebar context menu and start focused test await page.locator('[data-item-id="addons-group"]').hover(); - await page - .locator( - '[data-item-id="addons-group"] button[data-testid="context-menu"]' - ) - .click(); - const sidebarContextMenu = page.getByRole("dialog"); - await sidebarContextMenu.getByLabel("Start test run").click(); + await page.locator('[data-item-id="addons-group"] button[data-testid="context-menu"]').click(); + const sidebarContextMenu = page.getByRole('dialog'); + await sidebarContextMenu.getByLabel('Start test run').click(); // Assert - 1 failing test shows as a failed status await expect( - sidebarContextMenu.getByLabel( - "Component tests failed (2 errors or warnings so far)" - ) + sidebarContextMenu.getByLabel('Component tests failed (2 errors or warnings so far)') ).toBeVisible(); - await expect( - sidebarContextMenu.getByLabel("Component tests failed") - ).toHaveCount(1); + await expect(sidebarContextMenu.getByLabel('Component tests failed')).toHaveCount(1); // HACK: the testing module popover has poor tracking of focus due to how many disabled // buttons it has and how deeply it changes its UI on events. This would be solved once // we move to a declarative menu, and there's an ongoing PR for that. Until then, we tab // around to reset focus. - await page.keyboard.press("Tab"); - await page.keyboard.press("Escape"); - await page.click("body"); + await page.keyboard.press('Tab'); + await page.keyboard.press('Escape'); + await page.click('body'); await expect(sidebarContextMenu).not.toBeVisible(); // Assert - Tests are running and reported - await expect(page.locator("#testing-module-description")).toContainText( - "Ran 11 tests", - { timeout: 30000 } - ); + await expect(page.locator('#testing-module-description')).toContainText('Ran 11 tests', { + timeout: 30000, + }); await expect( page.locator( '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: error"]' @@ -734,96 +594,73 @@ test.describe("component testing", () => { ).toHaveCount(4); // 1 visible/expanded story, 1 expanded component, 1 collapsed component, 1 group }); - test("should run focused tests without coverage, even when enabled", async ({ + test('should run focused tests without coverage, even when enabled', async ({ page, browserName, }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); // Arrange - Prepare Storybook - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, false) - ); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, false)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("example/button", "CSF 3 Primary"); + await sbPage.navigateToStory('example/button', 'CSF 3 Primary'); - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "foo" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'foo' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); // Act - Enable coverage - await page.getByLabel("Coverage", { exact: true }).click(); + await page.getByLabel('Coverage', { exact: true }).click(); // Wait for Vitest to have (re)started await page.waitForTimeout(2000); // Act - Open sidebar context menu and start focused test + await page.locator('[data-item-id="example-button--csf-3-primary"]').hover(); await page - .locator('[data-item-id="example-button--csf-3-primary"]') - .hover(); - await page - .locator( - '[data-item-id="example-button--csf-3-primary"] button[data-testid="context-menu"]' - ) + .locator('[data-item-id="example-button--csf-3-primary"] button[data-testid="context-menu"]') .click(); - const sidebarContextMenu = page.getByRole("dialog"); - await sidebarContextMenu.getByLabel("Start test run").click(); + const sidebarContextMenu = page.getByRole('dialog'); + await sidebarContextMenu.getByLabel('Start test run').click(); // HACK: the testing module popover has poor tracking of focus due to how many disabled // buttons it has and how deeply it changes its UI on events. This would be solved once // we move to a declarative menu, and there's an ongoing PR for that. Until then, we tab // around to reset focus. - await page.keyboard.press("Tab"); - await page.keyboard.press("Escape"); - await page.click("body"); + await page.keyboard.press('Tab'); + await page.keyboard.press('Escape'); + await page.click('body'); await expect(sidebarContextMenu).not.toBeVisible(); // Arrange - Wait for test to finish and unfocus sidebar context menu - await expect(page.locator("#testing-module-description")).toContainText( - "Ran 1 test", - { timeout: 30000 } - ); - await page.click("body"); + await expect(page.locator('#testing-module-description')).toContainText('Ran 1 test', { + timeout: 30000, + }); + await page.click('body'); // Assert - Coverage is not shown because Focused Tests shouldn't collect coverage - await expect(page.getByLabel("Open coverage report")).not.toBeVisible(); + await expect(page.getByLabel('Open coverage report')).not.toBeVisible(); // Act - Run ALL tests - await page.getByLabel("Start test run").click(); + await page.getByLabel('Start test run').click(); // Arrange - Wait for tests to finish - await expect(page.locator("#testing-module-description")).toContainText( - /Ran \d{2,} tests/, - { timeout: 30000 } - ); + await expect(page.locator('#testing-module-description')).toContainText(/Ran \d{2,} tests/, { + timeout: 30000, + }); // Assert - Coverage percentage is now collected and shown because running all tests automatically re-enables coverage - await expect(page.getByLabel("Open coverage report")).toBeVisible({ + await expect(page.getByLabel('Open coverage report')).toBeVisible({ timeout: 30000, }); - const sbPercentageText = await page - .getByLabel(/% coverage\)$/) - .textContent(); + const sbPercentageText = await page.getByLabel(/% coverage\)$/).textContent(); expect(sbPercentageText).toMatch(/^\d+%$/); - const sbPercentage = Number.parseInt( - sbPercentageText!.replace("%", "") ?? "" - ); + const sbPercentage = Number.parseInt(sbPercentageText!.replace('%', '') ?? ''); expect(sbPercentage).toBeGreaterThanOrEqual(0); expect(sbPercentage).toBeLessThanOrEqual(100); }); - test.fixme( - "should still collect statuses even when the browser is closed", - () => {} - ); + test.fixme('should still collect statuses even when the browser is closed', () => {}); - test.fixme( - "should have correct status count globally and in context menus", - () => {} - ); + test.fixme('should have correct status count globally and in context menus', () => {}); - test.fixme( - "should open the correct component test and a11y panels when clicking on statuses", - () => {} - ); + test.fixme('should open the correct component test and a11y panels when clicking on statuses', () => {}); }); diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/save-from-controls.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/save-from-controls.spec.ts index 0e093e00d614..2eea8ca989b2 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/save-from-controls.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/save-from-controls.spec.ts @@ -1,55 +1,42 @@ -import { expect, test } from "@playwright/test"; -import process from "process"; +import { expect, test } from '@playwright/test'; +import process from 'process'; -import { SbPage } from "../../../../code/e2e-tests/util"; +import { SbPage } from '../../../../code/e2e-tests/util'; -const STORYBOOK_URL = "http://localhost:6006"; -const type = process.env.STORYBOOK_TYPE || "dev"; +const STORYBOOK_URL = 'http://localhost:6006'; +const type = process.env.STORYBOOK_TYPE || 'dev'; -test.describe("save-from-controls", () => { - test.describe.configure({ mode: "serial" }); - test.skip( - type === "build", - `Skipping save-from-controls tests for production Storybooks` - ); +test.describe('save-from-controls', () => { + test.describe.configure({ mode: 'serial' }); + test.skip(type === 'build', `Skipping save-from-controls tests for production Storybooks`); - test("Should be able to update a story", async ({ page, browserName }) => { + test('Should be able to update a story', async ({ page, browserName }) => { // this is needed because the e2e test will generate a new file in the system // which we don't know of its location (it runs in different sandboxes) // so we just create a random id to make it easier to run tests const id = Math.random().toString(36).substring(7); - test.skip( - browserName !== "chromium", - `Skipping save-from-controls tests for ${browserName}` - ); + test.skip(browserName !== 'chromium', `Skipping save-from-controls tests for ${browserName}`); - await page.goto(STORYBOOK_URL + "/?path=/story/example-mybutton--primary"); + await page.goto(STORYBOOK_URL + '/?path=/story/example-mybutton--primary'); const sbPage = new SbPage(page, expect); await sbPage.waitUntilLoaded(); - await sbPage.viewAddonPanel("Controls"); + await sbPage.viewAddonPanel('Controls'); // Update an arg - const label = sbPage.panelContent().locator("textarea[name=children]"); + const label = sbPage.panelContent().locator('textarea[name=children]'); await label.fill(`"Updated ${id}"`); await label.blur(); // Assert the footer is shown - await sbPage - .panelContent() - .locator('[data-short-label="Unsaved changes"]') - .isVisible(); + await sbPage.panelContent().locator('[data-short-label="Unsaved changes"]').isVisible(); // update the story - await sbPage - .panelContent() - .locator("button") - .getByText("Update story") - .click(); + await sbPage.panelContent().locator('button').getByText('Update story').click(); // Assert the file is saved - const notification1 = sbPage.page.getByTitle("Story saved"); + const notification1 = sbPage.page.getByTitle('Story saved'); await expect(notification1).toBeVisible(); // dismiss @@ -62,10 +49,7 @@ test.describe("save-from-controls", () => { await label.blur(); // Assert the footer is shown - await sbPage - .panelContent() - .locator('[data-short-label="Unsaved changes"]') - .isVisible(); + await sbPage.panelContent().locator('[data-short-label="Unsaved changes"]').isVisible(); const buttons = sbPage .panelContent() @@ -74,13 +58,11 @@ test.describe("save-from-controls", () => { // clone the story await buttons.click(); - await sbPage.page - .getByPlaceholder("Story export name") - .fill("ClonedStory" + id); - await sbPage.page.getByRole("button", { exact: true, name: "Create" }).click(); + await sbPage.page.getByPlaceholder('Story export name').fill('ClonedStory' + id); + await sbPage.page.getByRole('button', { exact: true, name: 'Create' }).click(); // Assert the file is saved - const notification2 = sbPage.page.getByTitle("Story created"); + const notification2 = sbPage.page.getByTitle('Story created'); await expect(notification2).toBeVisible(); await notification2.click(); diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/playwright-e2e.config.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/playwright-e2e.config.ts index c1b06b44c09b..ab73c12e8a1f 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/playwright-e2e.config.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/playwright-e2e.config.ts @@ -1,12 +1,12 @@ -import { defineConfig, devices } from "@playwright/test"; -import path from "node:path"; +import { defineConfig, devices } from '@playwright/test'; +import path from 'node:path'; /** * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: "./e2e-tests", - outputDir: "./test-results", + testDir: './e2e-tests', + outputDir: './test-results', /* Maximum time one test can run for. */ timeout: (process.env.CI ? 60 : 30) * 1000, /* Run tests in files in parallel */ @@ -19,19 +19,26 @@ export default defineConfig({ workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: [ - ["line"], + ['line'], [ - "junit", + 'junit', { embedAnnotationsAsProperties: true, - outputFile: path.join(__dirname, "..", "..", "..", "test-results", "react-vitest3-e2e-ui.xml"), + outputFile: path.join( + __dirname, + '..', + '..', + '..', + 'test-results', + 'react-vitest3-e2e-ui.xml' + ), }, ], ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: "retain-on-failure", + trace: 'retain-on-failure', // video: "retain-on-failure", // headless: false, }, @@ -39,8 +46,8 @@ export default defineConfig({ /* Configure projects for major browsers */ projects: [ { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, }, // { // name: 'firefox', @@ -53,8 +60,8 @@ export default defineConfig({ ], webServer: { - command: "yarn storybook", - url: "http://127.0.0.1:6006", + command: 'yarn storybook', + url: 'http://127.0.0.1:6006', reuseExistingServer: true, }, }); diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/pre-e2e.js b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/pre-e2e.js index a684b479b9ce..f061802f88b1 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/pre-e2e.js +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/pre-e2e.js @@ -1,10 +1,10 @@ /* eslint-disable no-undef */ /* eslint-disable @typescript-eslint/no-var-requires */ -const fs = require("node:fs"); -const path = require("node:path"); +const fs = require('node:fs'); +const path = require('node:path'); -const testStoryPath = path.resolve("stories/AddonTest.stories.tsx"); +const testStoryPath = path.resolve('stories/AddonTest.stories.tsx'); console.log(`Pre-e2e script: clearing ${testStoryPath}`); const storyContent = fs.readFileSync(testStoryPath).toString(); -fs.writeFileSync(testStoryPath, storyContent.replace("forceFailure: true", "forceFailure: false")); \ No newline at end of file +fs.writeFileSync(testStoryPath, storyContent.replace('forceFailure: true', 'forceFailure: false')); diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/AddonTest.stories.tsx b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/AddonTest.stories.tsx index fcb59a0ff94e..42e1be7a0495 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/AddonTest.stories.tsx +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/AddonTest.stories.tsx @@ -17,9 +17,12 @@ export default meta; type Story = StoryObj; -const { pass } = instrument({ - pass: async () => {}, -}, { intercept: true }) +const { pass } = instrument( + { + pass: async () => {}, + }, + { intercept: true } +); export const ExpectedFailure: Story = { args: { @@ -27,16 +30,16 @@ export const ExpectedFailure: Story = { }, play: async ({ args }) => { await pass(); - if(args.forceFailure) { + if (args.forceFailure) { throw new Error('Expected failure'); } - } + }, }; export const ExpectedSuccess: Story = { play: async () => { await pass(); - } + }, }; export const ExpectedContent: Story = { @@ -45,7 +48,7 @@ export const ExpectedContent: Story = { await expect(button).toHaveTextContent('test'); const decoratorString = within(canvasElement).getByTestId('decorator-string'); await expect(decoratorString).toHaveTextContent('Global Decorator'); - } + }, }; export const LongRunning: Story = { @@ -56,17 +59,17 @@ export const LongRunning: Story = { export const MismatchFailure: Story = { play: async () => { await pass(); - if(!globalThis.__vitest_browser__) { + if (!globalThis.__vitest_browser__) { throw new Error('Expected failure'); } - } + }, }; // Tests will fail in browser, but pass in CLI export const MismatchSuccess: Story = { play: async () => { await pass(); - if(globalThis.__vitest_browser__) { + if (globalThis.__vitest_browser__) { throw new Error('Unexpected success'); } }, @@ -80,21 +83,21 @@ export const PreviewHeadTest: Story = { expect(styles.backgroundColor).toBe('rgb(250, 250, 210)'); // set in main.js#previewHead expect(styles.borderColor).toBe('rgb(255, 0, 0)'); - } + }, }; export const StaticDirTest: Story = { play: async () => { const path = '/test-static-dirs/static.js'; - const { staticFunction } = await import(/* @vite-ignore */path); + const { staticFunction } = await import(/* @vite-ignore */ path); expect(staticFunction()).toBe(true); - } -} + }, +}; export const ViteFinalTest: Story = { play: async () => { // @ts-expect-error TS doesn't know about the alias const { aliasedFunction } = await import('test-alias'); expect(aliasedFunction()).toBe(true); - } -} + }, +}; diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.stories.tsx b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.stories.tsx index c40e163b0d32..1ca5894e791f 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.stories.tsx +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.stories.tsx @@ -63,7 +63,7 @@ CSF2StoryWithParamsAndDecorator.decorators = [(StoryFn) => ]; export const CSF3Primary: CSF3Story = { args: { - children: "foo", + children: 'foo', size: 'large', primary: true, }, @@ -110,4 +110,4 @@ export const WithLoader: CSF3Story<{ mockFn: (val: string) => string }> = { play: async () => { expect(mockFn).toHaveBeenCalledWith('render'); }, -}; \ No newline at end of file +}; diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.tsx b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.tsx index 8f5d125bb293..58dc199388ec 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.tsx +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.tsx @@ -30,7 +30,7 @@ export interface ButtonProps { export const Button: React.FC = (props) => { const { primary = false, size = 'medium', backgroundColor, children, ...otherProps } = props; const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary'; - console.log({props}) + console.log({ props }); return ( +const Component = () => ; const meta = { title: 'Addons/Group/Other', @@ -11,8 +11,7 @@ export default meta; type Story = StoryObj; -export const Passes: Story = { -}; +export const Passes: Story = {}; export const Fails: Story = { play: async () => { diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/get-button-string.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/get-button-string.ts index 7c9d9ab153d0..c6a83a8018de 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/get-button-string.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/get-button-string.ts @@ -1,3 +1,3 @@ export const getButtonString = () => { - return "test"; + return 'test'; }; diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vite.config.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vite.config.ts index 9cc50ead1c0a..627a3196243d 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vite.config.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vite.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; // https://vitejs.dev/config/ export default defineConfig({ diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vitest.workspace.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vitest.workspace.ts index d09ebcb58a9b..d39cfd0e0467 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vitest.workspace.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vitest.workspace.ts @@ -1,23 +1,23 @@ -import { defineWorkspace } from "vitest/config"; -import { storybookTest } from "@storybook/addon-vitest/vitest-plugin"; +import { defineWorkspace } from 'vitest/config'; +import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; export default defineWorkspace([ { - extends: "vite.config.ts", + extends: 'vite.config.ts', plugins: [ storybookTest( process.env.SKIP_FAIL_ON_PURPOSE ? { tags: { - exclude: ["fail-on-purpose"], + exclude: ['fail-on-purpose'], }, } : undefined ), ], test: { - name: "storybook", - pool: "threads", + name: 'storybook', + pool: 'threads', deps: { optimizer: { web: { @@ -27,16 +27,16 @@ export default defineWorkspace([ }, browser: { enabled: true, - provider: "playwright", + provider: 'playwright', headless: true, instances: [ { - browser: "chromium", + browser: 'chromium', }, ], }, - setupFiles: ["./.storybook/vitest.setup.ts"], - environment: "jsdom", + setupFiles: ['./.storybook/vitest.setup.ts'], + environment: 'jsdom', }, }, ]); diff --git a/test-storybooks/portable-stories-kitchen-sink/react/.eslintrc.cjs b/test-storybooks/portable-stories-kitchen-sink/react/.eslintrc.cjs index 29cb6d5a0877..f51e2baf0b82 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/.eslintrc.cjs +++ b/test-storybooks/portable-stories-kitchen-sink/react/.eslintrc.cjs @@ -1,14 +1,16 @@ module.exports = { root: true, env: { browser: true, es2020: true }, - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + 'plugin:storybook/recommended', + ], ignorePatterns: ['dist', '.eslintrc.cjs'], parser: '@typescript-eslint/parser', plugins: ['react-refresh'], rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], }, -} +}; diff --git a/test-storybooks/portable-stories-kitchen-sink/react/.storybook/get-decorator-string.ts b/test-storybooks/portable-stories-kitchen-sink/react/.storybook/get-decorator-string.ts index 794f14db5674..44efef9ba0ee 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/.storybook/get-decorator-string.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/.storybook/get-decorator-string.ts @@ -1,3 +1,3 @@ export const getDecoratorString = () => { - return "Global Decorator"; + return 'Global Decorator'; }; diff --git a/test-storybooks/portable-stories-kitchen-sink/react/.storybook/main.ts b/test-storybooks/portable-stories-kitchen-sink/react/.storybook/main.ts index fa0ee95488bf..969ee138815e 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/.storybook/main.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/.storybook/main.ts @@ -1,24 +1,24 @@ -import { join } from "node:path"; +import { join } from 'node:path'; -import type { StorybookConfig } from "@storybook/react-vite"; +import type { StorybookConfig } from '@storybook/react-vite'; const config: StorybookConfig = { - stories: ["../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)"], - addons: ["@storybook/addon-vitest", "@storybook/addon-a11y"], + stories: ['../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + addons: ['@storybook/addon-vitest', '@storybook/addon-a11y'], framework: { - name: "@storybook/react-vite", + name: '@storybook/react-vite', options: {}, }, core: { disableWhatsNewNotifications: true, }, - previewHead: (head = "") => `${head} + previewHead: (head = '') => `${head} `, - staticDirs: [{ from: "./test-static-dirs", to: "test-static-dirs" }], + staticDirs: [{ from: './test-static-dirs', to: 'test-static-dirs' }], viteFinal: (config) => { return { ...config, @@ -30,19 +30,19 @@ const config: StorybookConfig = { ...config.resolve, alias: { ...config.resolve?.alias, - "test-alias": join(import.meta.dirname, "aliased.ts"), + 'test-alias': join(import.meta.dirname, 'aliased.ts'), }, }, }; }, refs: { - "storybook@8.0.0": { - title: "Storybook 8.0.0", - url: "https://635781f3500dd2c49e189caf-gckybvsekn.chromatic.com/", + 'storybook@8.0.0': { + title: 'Storybook 8.0.0', + url: 'https://635781f3500dd2c49e189caf-gckybvsekn.chromatic.com/', }, - "storybook@7.6.18": { - title: "Storybook 7.6.18", - url: "https://635781f3500dd2c49e189caf-oljwjdrftz.chromatic.com/", + 'storybook@7.6.18': { + title: 'Storybook 7.6.18', + url: 'https://635781f3500dd2c49e189caf-oljwjdrftz.chromatic.com/', }, }, }; diff --git a/test-storybooks/portable-stories-kitchen-sink/react/.storybook/setup-file-dependency.ts b/test-storybooks/portable-stories-kitchen-sink/react/.storybook/setup-file-dependency.ts index 592e8f52b66b..d433f58cf120 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/.storybook/setup-file-dependency.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/.storybook/setup-file-dependency.ts @@ -1,3 +1,3 @@ export const getString = () => { - return "initial string"; + return 'initial string'; }; diff --git a/test-storybooks/portable-stories-kitchen-sink/react/cypress.config.ts b/test-storybooks/portable-stories-kitchen-sink/react/cypress.config.ts index c4ef4f529b25..0130bcf61384 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/cypress.config.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/cypress.config.ts @@ -1,11 +1,11 @@ -import { defineConfig } from "cypress"; +import { defineConfig } from 'cypress'; export default defineConfig({ screenshotOnRunFailure: false, component: { devServer: { - framework: "react", - bundler: "vite", + framework: 'react', + bundler: 'vite', }, }, }); diff --git a/test-storybooks/portable-stories-kitchen-sink/react/cypress/support/commands.ts b/test-storybooks/portable-stories-kitchen-sink/react/cypress/support/commands.ts index 2ebd0f10df14..50fc1ebf5c5a 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/cypress/support/commands.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/cypress/support/commands.ts @@ -7,4 +7,4 @@ // For more comprehensive examples of custom // commands please read more here: // https://on.cypress.io/custom-commands -// *********************************************** \ No newline at end of file +// *********************************************** diff --git a/test-storybooks/portable-stories-kitchen-sink/react/cypress/support/component-index.html b/test-storybooks/portable-stories-kitchen-sink/react/cypress/support/component-index.html index ac6e79fd83df..faf3b5f43b10 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/cypress/support/component-index.html +++ b/test-storybooks/portable-stories-kitchen-sink/react/cypress/support/component-index.html @@ -1,12 +1,12 @@ - + - - - + + + Components App
- \ No newline at end of file + diff --git a/test-storybooks/portable-stories-kitchen-sink/react/cypress/support/component.ts b/test-storybooks/portable-stories-kitchen-sink/react/cypress/support/component.ts index 455075197889..127f26e7645b 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/cypress/support/component.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/cypress/support/component.ts @@ -15,12 +15,12 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import "./commands"; +import './commands'; -import { mount } from "cypress/react18"; +import { mount } from 'cypress/react18'; -import { setProjectAnnotations } from "@storybook/react-vite"; -import sbAnnotations from "../../.storybook/preview"; +import { setProjectAnnotations } from '@storybook/react-vite'; +import sbAnnotations from '../../.storybook/preview'; // Augment the Cypress namespace to include type definitions for // your custom command. @@ -40,6 +40,6 @@ declare global { // which will break process.env = {}; -Cypress.Commands.add("mount", mount); +Cypress.Commands.add('mount', mount); setProjectAnnotations([sbAnnotations]); diff --git a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts index 39739f22b74a..4fb2c24047c0 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts @@ -1,40 +1,30 @@ -import { promises as fs } from "node:fs"; -import path from "node:path"; +import { promises as fs } from 'node:fs'; +import path from 'node:path'; -import { expect, test } from "@playwright/test"; +import { expect, test } from '@playwright/test'; -import { SbPage } from "../../../../code/e2e-tests/util"; +import { SbPage } from '../../../../code/e2e-tests/util'; -const STORYBOOK_URL = "http://localhost:6006"; -const TEST_STORY_PATH = path.resolve( - __dirname, - "..", - "stories", - "AddonTest.stories.tsx" -); +const STORYBOOK_URL = 'http://localhost:6006'; +const TEST_STORY_PATH = path.resolve(__dirname, '..', 'stories', 'AddonTest.stories.tsx'); const UNHANDLED_ERRORS_STORY_PATH = path.resolve( __dirname, - "..", - "stories", - "UnhandledErrors.stories.tsx" -); -const ADDON_TEST_DEPENDENCY_PATH = path.resolve( - __dirname, - "..", - "stories", - "get-button-string.ts" + '..', + 'stories', + 'UnhandledErrors.stories.tsx' ); +const ADDON_TEST_DEPENDENCY_PATH = path.resolve(__dirname, '..', 'stories', 'get-button-string.ts'); const PREVIEW_DEPENDENCY_PATH = path.resolve( __dirname, - "..", - ".storybook", - "get-decorator-string.ts" + '..', + '.storybook', + 'get-decorator-string.ts' ); const SETUP_FILE_DEPENDENCY_PATH = path.resolve( __dirname, - "..", - ".storybook", - "setup-file-dependency.ts" + '..', + '.storybook', + 'setup-file-dependency.ts' ); const setForceFailureFlag = (content: string, value: boolean) => @@ -42,10 +32,7 @@ const setForceFailureFlag = (content: string, value: boolean) => const modifiedFiles = new Map(); -const modifyFile = async ( - filePath: string, - modify: (content: string) => string -) => { +const modifyFile = async (filePath: string, modify: (content: string) => string) => { const content = (await fs.readFile(filePath)).toString(); const modifiedContent = modify(content); await fs.writeFile(filePath, modifiedContent); @@ -66,8 +53,8 @@ const restoreAllFiles = async () => { await new Promise((resolve) => setTimeout(resolve, 2000)); }; -test.describe("component testing", () => { - test.describe.configure({ mode: "serial" }); +test.describe('component testing', () => { + test.describe.configure({ mode: 'serial' }); test.beforeEach(async ({ page }) => { const sbPage = new SbPage(page, expect); @@ -75,24 +62,20 @@ test.describe("component testing", () => { await page.evaluate(() => window.sessionStorage.clear()); await sbPage.waitUntilLoaded(); - const expandTestingModule = page.getByLabel("Expand testing module"); + const expandTestingModule = page.getByLabel('Expand testing module'); if (await expandTestingModule.isVisible()) { await expandTestingModule.click(); } }); test.afterEach(async ({ page }) => { - await page.click("body"); + await page.click('body'); try { - const descriptionButton = page.locator("#testing-module-description button"); - if ( - await descriptionButton.isVisible({ timeout: 4000 }).catch(() => false) - ) { + const descriptionButton = page.locator('#testing-module-description button'); + if (await descriptionButton.isVisible({ timeout: 4000 }).catch(() => false)) { await descriptionButton.click({ timeout: 4000, force: true }); - await page - .getByLabel("Close modal") - .click({ timeout: 4000, force: true }); + await page.getByLabel('Close modal').click({ timeout: 4000, force: true }); } } catch { // Ignore any errors when trying to open the modal @@ -100,31 +83,31 @@ test.describe("component testing", () => { await restoreAllFiles(); - const expandTestingModule = page.getByLabel("Expand testing module"); + const expandTestingModule = page.getByLabel('Expand testing module'); if (await expandTestingModule.isVisible()) { await expandTestingModule.click(); } // Make sure any popover is closed - await page.click("body"); + await page.click('body'); // Ensure that all test results are removed and features are disabled, as previous tests might have enabled them - const clearStatusesButton = page.getByLabel("Clear all statuses"); + const clearStatusesButton = page.getByLabel('Clear all statuses'); if (await clearStatusesButton.isVisible()) { await clearStatusesButton.click(); } - const watchModeToggle = page.getByRole("switch", { name: "Watch mode" }); + const watchModeToggle = page.getByRole('switch', { name: 'Watch mode' }); if ( (await watchModeToggle.isVisible()) && - (await watchModeToggle.getAttribute("aria-checked")) === "true" + (await watchModeToggle.getAttribute('aria-checked')) === 'true' ) { await watchModeToggle.click(); } const configs = [ - page.getByRole("checkbox", { name: "Coverage" }), - page.getByRole("checkbox", { name: "Accessibility" }), + page.getByRole('checkbox', { name: 'Coverage' }), + page.getByRole('checkbox', { name: 'Accessibility' }), ]; for (const config of configs) { if (await config.isChecked()) { @@ -133,38 +116,31 @@ test.describe("component testing", () => { } }); - test("should show discrepancy between test results", async ({ - page, - browserName, - }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test('should show discrepancy between test results', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); test.setTimeout(40_000); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Mismatch Failure"); + await sbPage.navigateToStory('addons/group/test', 'Mismatch Failure'); // For whatever reason, sometimes it takes longer for the story to load - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); - await sbPage.viewAddonPanel("Interactions"); + await sbPage.viewAddonPanel('Interactions'); // For whatever reason, when visiting a story sometimes the story element is collapsed and that causes flake - const testStoryElement = await page.getByRole("button", { - name: "Test", + const testStoryElement = await page.getByRole('button', { + name: 'Test', exact: true, }); - if ((await testStoryElement.getAttribute("aria-expanded")) !== "true") { + if ((await testStoryElement.getAttribute('aria-expanded')) !== 'true') { testStoryElement.click(); } - const testingModuleDescription = await page.locator( - "#testing-module-description" - ); + const testingModuleDescription = await page.locator('#testing-module-description'); - const runTestsButton = await page.getByLabel("Start test run"); + const runTestsButton = await page.getByLabel('Start test run'); await runTestsButton.click(); await expect(testingModuleDescription).not.toContainText(/Ran \d+ tests/, { @@ -176,102 +152,75 @@ test.describe("component testing", () => { timeout: 60000, }); - const errorFilter = page.getByLabel( - /Filter main navigation to show \d+ tests with errors/ - ); + const errorFilter = page.getByLabel(/Filter main navigation to show \d+ tests with errors/); await expect(errorFilter).toBeVisible(); // Assert discrepancy: CLI pass + Browser fail const failingStoryElement = page.locator( '[data-item-id="addons-group-test--mismatch-failure"] [data-testid="tree-status-button"]' ); - await expect(failingStoryElement).toHaveAttribute( - "aria-label", - "Test status: success" - ); + await expect(failingStoryElement).toHaveAttribute('aria-label', 'Test status: success'); await expect(sbPage.panelContent()).toContainText( /This interaction test passed in the CLI, but the tests failed in this browser/ ); // Assert discrepancy: CLI fail + Browser pass - await sbPage.navigateToStory("addons/group/test", "Mismatch Success"); + await sbPage.navigateToStory('addons/group/test', 'Mismatch Success'); const successfulStoryElement = page.locator( '[data-item-id="addons-group-test--mismatch-success"] [data-testid="tree-status-button"]' ); - await expect(successfulStoryElement).toHaveAttribute( - "aria-label", - "Test status: error" - ); + await expect(successfulStoryElement).toHaveAttribute('aria-label', 'Test status: error'); await expect(sbPage.panelContent()).toContainText( /This interaction test passed in this browser, but the tests failed in the CLI/ ); }); - test("should execute tests via testing module UI", async ({ - page, - browserName, - }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, true) - ); + test('should execute tests via testing module UI', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, true)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); // For whatever reason, sometimes it takes longer for the story to load - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); - await expect(page.locator("#testing-module-title")).toHaveText( - "Run component tests" - ); + await expect(page.locator('#testing-module-title')).toHaveText('Run component tests'); - const runTestsButton = await page.getByLabel("Start test run"); - const watchModeButton = await page.getByRole("switch", { - name: "Watch mode", + const runTestsButton = await page.getByLabel('Start test run'); + const watchModeButton = await page.getByRole('switch', { + name: 'Watch mode', }); - await expect(runTestsButton).not.toHaveAttribute("aria-disabled", "true"); - await expect(watchModeButton).not.toHaveAttribute("aria-disabled", "true"); + await expect(runTestsButton).not.toHaveAttribute('aria-disabled', 'true'); + await expect(watchModeButton).not.toHaveAttribute('aria-disabled', 'true'); await runTestsButton.click(); // The test button will be disabled as tests are running - expect(watchModeButton).toHaveAttribute("aria-disabled", "true"), - - // Wait for test results to appear - await expect(page.locator("#testing-module-description")).toHaveText( - /Ran \d+ tests/, - { timeout: 30000 } - ); + (expect(watchModeButton).toHaveAttribute('aria-disabled', 'true'), + // Wait for test results to appear + await expect(page.locator('#testing-module-description')).toHaveText(/Ran \d+ tests/, { + timeout: 30000, + })); - await expect(runTestsButton).not.toHaveAttribute("aria-disabled", "true"); - await expect(watchModeButton).not.toHaveAttribute("aria-disabled", "true"); + await expect(runTestsButton).not.toHaveAttribute('aria-disabled', 'true'); + await expect(watchModeButton).not.toHaveAttribute('aria-disabled', 'true'); - const errorFilter = page.getByLabel( - /Filter main navigation to show \d+ tests with errors/ - ); + const errorFilter = page.getByLabel(/Filter main navigation to show \d+ tests with errors/); await expect(errorFilter).toBeVisible(); // Assert for expected success const successfulStoryElement = page.locator( '[data-item-id="addons-group-test--expected-success"] [data-testid="tree-status-button"]' ); - await expect(successfulStoryElement).toHaveAttribute( - "aria-label", - "Test status: success" - ); + await expect(successfulStoryElement).toHaveAttribute('aria-label', 'Test status: success'); // Assert for expected failure const failingStoryElement = page.locator( '[data-item-id="addons-group-test--expected-failure"] [data-testid="tree-status-button"]' ); - await expect(failingStoryElement).toHaveAttribute( - "aria-label", - "Test status: error" - ); + await expect(failingStoryElement).toHaveAttribute('aria-label', 'Test status: error'); // Assert that filter works as intended await errorFilter.click(); @@ -282,55 +231,41 @@ test.describe("component testing", () => { await expect(sidebarItems).toHaveCount(2); }); - test("should run tests in watch mode when a story file is changed", async ({ + test('should run tests in watch mode when a story file is changed', async ({ page, browserName, }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, false) - ); + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, false)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); // For whatever reason, sometimes it takes longer for the story to load - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); - await page.getByRole("switch", { name: "Watch mode" }).click(); + await page.getByRole('switch', { name: 'Watch mode' }).click(); // We shouldn't have to do an arbitrary wait, but because there is no UI for loading state yet, we have to await page.waitForTimeout(8000); - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, true) - ); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, true)); // Wait for test results to appear - const errorFilter = page.getByLabel( - /Filter main navigation to show \d+ tests with errors/ - ); + const errorFilter = page.getByLabel(/Filter main navigation to show \d+ tests with errors/); await expect(errorFilter).toBeVisible({ timeout: 30000 }); // Assert for expected success const successfulStoryElement = page.locator( '[data-item-id="addons-group-test--expected-success"] [data-testid="tree-status-button"]' ); - await expect(successfulStoryElement).toHaveAttribute( - "aria-label", - "Test status: success" - ); + await expect(successfulStoryElement).toHaveAttribute('aria-label', 'Test status: success'); // Assert for expected failure const failingStoryElement = page.locator( '[data-item-id="addons-group-test--expected-failure"] [data-testid="tree-status-button"]' ); - await expect(failingStoryElement).toHaveAttribute( - "aria-label", - "Test status: error" - ); + await expect(failingStoryElement).toHaveAttribute('aria-label', 'Test status: error'); // Assert that filter works as intended await errorFilter.click(); @@ -345,211 +280,171 @@ test.describe("component testing", () => { page, browserName, }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); // For whatever reason, sometimes it takes longer for the story to load - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); - await page.getByRole("switch", { name: "Watch mode" }).click(); + await page.getByRole('switch', { name: 'Watch mode' }).click(); // We shouldn't have to do an arbitrary wait, but because there is no UI for loading state yet, we have to await page.waitForTimeout(3000); - await modifyFile(ADDON_TEST_DEPENDENCY_PATH, (content) => - content.replace("test", "changed") - ); + await modifyFile(ADDON_TEST_DEPENDENCY_PATH, (content) => content.replace('test', 'changed')); // Expect less than 10 tests to have run - await expect(page.locator("#testing-module-description")).toContainText( - /Ran \d tests/, - { timeout: 30000 } - ); + await expect(page.locator('#testing-module-description')).toContainText(/Ran \d tests/, { + timeout: 30000, + }); // Assert for expected failure const failingStoryElement = page.locator( '[data-item-id="addons-group-test--expected-content"] [data-testid="tree-status-button"]' ); - await expect(failingStoryElement).toHaveAttribute( - "aria-label", - "Test status: error" - ); + await expect(failingStoryElement).toHaveAttribute('aria-label', 'Test status: error'); }); test("should run all tests in watch mode when the preview file's dependency is changed", async ({ page, browserName, }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); // For whatever reason, sometimes it takes longer for the story to load - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); - await page.getByRole("switch", { name: "Watch mode" }).click(); + await page.getByRole('switch', { name: 'Watch mode' }).click(); // We shouldn't have to do an arbitrary wait, but because there is no UI for loading state yet, we have to await page.waitForTimeout(3000); await modifyFile(PREVIEW_DEPENDENCY_PATH, (content) => - content.replace("Global Decorator", "Changed Decorator") + content.replace('Global Decorator', 'Changed Decorator') ); // Expect at least 20 tests to have run - await expect(page.locator("#testing-module-description")).toContainText( - /Ran [2-9]\d tests/, - { timeout: 30000 } - ); + await expect(page.locator('#testing-module-description')).toContainText(/Ran [2-9]\d tests/, { + timeout: 30000, + }); // Assert for expected failure const failingStoryElement = page.locator( '[data-item-id="addons-group-test--expected-content"] [data-testid="tree-status-button"]' ); - await expect(failingStoryElement).toHaveAttribute( - "aria-label", - "Test status: error" - ); + await expect(failingStoryElement).toHaveAttribute('aria-label', 'Test status: error'); }); test("should run all tests in watch mode when the setup file's dependency is changed", async ({ page, browserName, }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); // For whatever reason, sometimes it takes longer for the story to load - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); - await page.getByRole("switch", { name: "Watch mode" }).click(); + await page.getByRole('switch', { name: 'Watch mode' }).click(); // We shouldn't have to do an arbitrary wait, but because there is no UI for loading state yet, we have to await page.waitForTimeout(3000); await modifyFile(SETUP_FILE_DEPENDENCY_PATH, (content) => - content.replace("initial string", "changed string") + content.replace('initial string', 'changed string') ); // Expect at least 20 tests to have run - await expect(page.locator("#testing-module-description")).toContainText( - /Ran [2-9]\d tests/, - { timeout: 30000 } - ); + await expect(page.locator('#testing-module-description')).toContainText(/Ran [2-9]\d tests/, { + timeout: 30000, + }); }); - test("should collect coverage to testing module and HTML report", async ({ + test('should collect coverage to testing module and HTML report', async ({ page, browserName, }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); // Arrange - Prepare Storybook - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, false) - ); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, false)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); // Assert - No coverage report initially - await expect(page.getByLabel("Open coverage report")).toHaveCount(0); + await expect(page.getByLabel('Open coverage report')).toHaveCount(0); // Act - Enable coverage and run tests - await page.getByLabel("Coverage", { exact: true }).click(); + await page.getByLabel('Coverage', { exact: true }).click(); // Wait for Vitest to have (re)started await page.waitForTimeout(2000); - await page.getByLabel("Start test run").click(); + await page.getByLabel('Start test run').click(); // Assert - Coverage report is collected and shown - await expect(page.getByLabel("Open coverage report")).toBeVisible({ + await expect(page.getByLabel('Open coverage report')).toBeVisible({ timeout: 30000, }); - const sbPercentageText = await page - .getByLabel(/% coverage\)$/) - .textContent(); + const sbPercentageText = await page.getByLabel(/% coverage\)$/).textContent(); expect(sbPercentageText).toMatch(/^\d+%$/); - const sbPercentage = Number.parseInt( - sbPercentageText!.replace("%", "") ?? "" - ); + const sbPercentage = Number.parseInt(sbPercentageText!.replace('%', '') ?? ''); expect(sbPercentage).toBeGreaterThanOrEqual(0); expect(sbPercentage).toBeLessThanOrEqual(100); // Act - Open HTML coverage report - const coverageReportLink = await page.getByLabel("Open coverage report"); + const coverageReportLink = await page.getByLabel('Open coverage report'); // Remove target="_blank" attribute to open in the same tab - await coverageReportLink.evaluate((elem) => elem.removeAttribute("target")); - await page.getByLabel("Open coverage report").click(); + await coverageReportLink.evaluate((elem) => elem.removeAttribute('target')); + await page.getByLabel('Open coverage report').click(); // Assert - HTML coverage report is accessible and reports the same coverage percentage as Storybook const htmlPercentageText = - (await page - .locator('span:has(+ :text("Statements"))') - .first() - .textContent()) ?? ""; - const htmlPercentage = Number.parseFloat( - htmlPercentageText.replace("% ", "") - ); + (await page.locator('span:has(+ :text("Statements"))').first().textContent()) ?? ''; + const htmlPercentage = Number.parseFloat(htmlPercentageText.replace('% ', '')); expect(Math.round(htmlPercentage)).toBe(sbPercentage); await page.goBack(); }); - test("should run focused test for a single story", async ({ - page, - browserName, - }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test('should run focused test for a single story', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); // Arrange - Prepare Storybook - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, false) - ); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, false)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); // Act - Open sidebar context menu, start focused test then close menu - await page - .locator('[data-item-id="addons-group-test--expected-failure"]') - .hover(); + await page.locator('[data-item-id="addons-group-test--expected-failure"]').hover(); await page .locator( '[data-item-id="addons-group-test--expected-failure"] button[data-testid="context-menu"]' ) .click(); - const sidebarContextMenu = page.getByRole("dialog"); - await sidebarContextMenu.getByLabel("Start test run").click(); + const sidebarContextMenu = page.getByRole('dialog'); + await sidebarContextMenu.getByLabel('Start test run').click(); // Assert - Only one test is running and reported - await expect( - sidebarContextMenu.locator("#testing-module-description") - ).toContainText("Ran 1 test", { timeout: 30000 }); - await expect( - sidebarContextMenu.getByLabel("Component tests passed") - ).toHaveCount(1); - await page.click("body"); + await expect(sidebarContextMenu.locator('#testing-module-description')).toContainText( + 'Ran 1 test', + { timeout: 30000 } + ); + await expect(sidebarContextMenu.getByLabel('Component tests passed')).toHaveCount(1); + await page.click('body'); await expect( page.locator( '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: success"]' @@ -557,110 +452,89 @@ test.describe("component testing", () => { ).toHaveCount(1); }); - test("should show unhandled errors in the testing module", async ({ - page, - browserName, - }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test('should show unhandled errors in the testing module', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); // Arrange - Prepare Storybook - await modifyFile(UNHANDLED_ERRORS_STORY_PATH, (content) => - setForceFailureFlag(content, true) - ); + await modifyFile(UNHANDLED_ERRORS_STORY_PATH, (content) => setForceFailureFlag(content, true)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("example/unhandlederrors", "Success"); + await sbPage.navigateToStory('example/unhandlederrors', 'Success'); - const storyElement = sbPage.getCanvasBodyElement().getByText("Hello world"); + const storyElement = sbPage.getCanvasBodyElement().getByText('Hello world'); await expect(storyElement).toBeVisible({ timeout: 30000 }); // Act - Open sidebar context menu and start focused test await page.locator('[data-item-id="example-unhandlederrors"]').hover(); await page - .locator( - '[data-item-id="example-unhandlederrors"] button[data-testid="context-menu"]' - ) + .locator('[data-item-id="example-unhandlederrors"] button[data-testid="context-menu"]') .click(); - const sidebarContextMenu = page.getByRole("dialog"); - await sidebarContextMenu.getByLabel("Start test run").click(); + const sidebarContextMenu = page.getByRole('dialog'); + await sidebarContextMenu.getByLabel('Start test run').click(); // HACK: the testing module popover has poor tracking of focus due to how many disabled // buttons it has and how deeply it changes its UI on events. This would be solved once // we move to a declarative menu, and there's an ongoing PR for that. Until then, we tab // around to reset focus. - await page.keyboard.press("Tab"); - await page.keyboard.press("Escape"); - await page.click("body"); + await page.keyboard.press('Tab'); + await page.keyboard.press('Escape'); + await page.click('body'); await expect(sidebarContextMenu).not.toBeVisible(); // Assert - Tests are running and errors are reported - const errorLink = page.locator("#testing-module-description button"); - await expect(errorLink).toContainText("View full error", { + const errorLink = page.locator('#testing-module-description button'); + await expect(errorLink).toContainText('View full error', { timeout: 30000, }); await errorLink.click(); - await expect(page.locator("pre")).toContainText( - "I THREW AN UNHANDLED ERROR!" - ); - await expect(page.locator("pre")).toContainText("This error originated in"); - await expect(page.locator("pre")).toContainText( + await expect(page.locator('pre')).toContainText('I THREW AN UNHANDLED ERROR!'); + await expect(page.locator('pre')).toContainText('This error originated in'); + await expect(page.locator('pre')).toContainText( "The latest test that might've caused the error is" ); - await page.getByLabel("Close modal").click(); + await page.getByLabel('Close modal').click(); }); - test("should run focused test for a component", async ({ - page, - browserName, - }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test('should run focused test for a component', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); // Arrange - Prepare Storybook - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, false) - ); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, false)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); // Act - Open sidebar context menu and start focused test await page.locator('[data-item-id="addons-group-test"]').hover(); await page - .locator( - '[data-item-id="addons-group-test"] button[data-testid="context-menu"]' - ) + .locator('[data-item-id="addons-group-test"] button[data-testid="context-menu"]') .click(); - const sidebarContextMenu = page.getByRole("dialog"); - await sidebarContextMenu.getByLabel("Start test run").click(); + const sidebarContextMenu = page.getByRole('dialog'); + await sidebarContextMenu.getByLabel('Start test run').click(); // Assert - Tests are running and reported - await expect( - sidebarContextMenu.locator("#testing-module-description") - ).toContainText("Ran 9 tests", { timeout: 30000 }); + await expect(sidebarContextMenu.locator('#testing-module-description')).toContainText( + 'Ran 9 tests', + { timeout: 30000 } + ); // Assert - Failing test shows as a failed status + await expect(sidebarContextMenu.getByLabel('Component tests failed')).toHaveCount(1); await expect( - sidebarContextMenu.getByLabel("Component tests failed") - ).toHaveCount(1); - await expect( - sidebarContextMenu.getByLabel( - "Component tests failed (1 errors or warnings so far)" - ) + sidebarContextMenu.getByLabel('Component tests failed (1 errors or warnings so far)') ).toBeVisible(); // HACK: the testing module popover has poor tracking of focus due to how many disabled // buttons it has and how deeply it changes its UI on events. This would be solved once // we move to a declarative menu, and there's an ongoing PR for that. Until then, we tab // around to reset focus. - await page.keyboard.press("Tab"); - await page.keyboard.press("Escape"); - await page.click("body"); + await page.keyboard.press('Tab'); + await page.keyboard.press('Escape'); + await page.click('body'); await expect(sidebarContextMenu).not.toBeVisible(); - await page.click("body"); + await page.click('body'); await expect( page.locator( '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: success"]' @@ -673,62 +547,49 @@ test.describe("component testing", () => { ).toHaveCount(3); // 1 story, 1 component, 1 group }); - test("should run focused test for a group", async ({ page, browserName }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test('should run focused test for a group', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); // Arrange - Prepare Storybook - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, false) - ); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, false)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("addons/group/test", "Expected Failure"); + await sbPage.navigateToStory('addons/group/test', 'Expected Failure'); - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "test" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'test' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); // Act - Open sidebar context menu and start focused test await page.locator('[data-item-id="addons-group"]').hover(); - await page - .locator( - '[data-item-id="addons-group"] button[data-testid="context-menu"]' - ) - .click(); - const sidebarContextMenu = page.getByRole("dialog"); - await sidebarContextMenu.getByLabel("Start test run").click(); + await page.locator('[data-item-id="addons-group"] button[data-testid="context-menu"]').click(); + const sidebarContextMenu = page.getByRole('dialog'); + await sidebarContextMenu.getByLabel('Start test run').click(); // Assert - 1 failing test shows as a failed status await expect( - sidebarContextMenu.getByLabel( - "Component tests failed (2 errors or warnings so far)" - ) + sidebarContextMenu.getByLabel('Component tests failed (2 errors or warnings so far)') ).toBeVisible(); - await expect( - sidebarContextMenu.getByLabel("Component tests failed") - ).toHaveCount(1); + await expect(sidebarContextMenu.getByLabel('Component tests failed')).toHaveCount(1); // HACK: the testing module popover has poor tracking of focus due to how many disabled // buttons it has and how deeply it changes its UI on events. This would be solved once // we move to a declarative menu, and there's an ongoing PR for that. Until then, we tab // around to reset focus. - await page.keyboard.press("Tab"); - await page.keyboard.press("Escape"); - await page.click("body"); + await page.keyboard.press('Tab'); + await page.keyboard.press('Escape'); + await page.click('body'); await expect(sidebarContextMenu).not.toBeVisible(); // Assert - Tests are running and reported - await expect(page.locator("#testing-module-description")).toContainText( - "Ran 11 tests", - { timeout: 30000 } - ); + await expect(page.locator('#testing-module-description')).toContainText('Ran 11 tests', { + timeout: 30000, + }); await expect( page.locator( '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: error"]' ) ).toHaveCount(4); // 1 visible/expanded story, 1 expanded component, 1 collapsed component, 1 group - await page.click("body"); + await page.click('body'); await expect( page.locator( '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: success"]' @@ -741,96 +602,73 @@ test.describe("component testing", () => { ).toHaveCount(4); }); - test("should run focused tests without coverage, even when enabled", async ({ + test('should run focused tests without coverage, even when enabled', async ({ page, browserName, }) => { - test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`); + test.skip(browserName !== 'chromium', `Skipping tests for ${browserName}`); // Arrange - Prepare Storybook - await modifyFile(TEST_STORY_PATH, (content) => - setForceFailureFlag(content, false) - ); + await modifyFile(TEST_STORY_PATH, (content) => setForceFailureFlag(content, false)); const sbPage = new SbPage(page, expect); - await sbPage.navigateToStory("example/button", "CSF 3 Primary"); + await sbPage.navigateToStory('example/button', 'CSF 3 Primary'); - const storyElement = sbPage - .getCanvasBodyElement() - .getByRole("button", { name: "foo" }); + const storyElement = sbPage.getCanvasBodyElement().getByRole('button', { name: 'foo' }); await expect(storyElement).toBeVisible({ timeout: 30000 }); // Act - Enable coverage - await page.getByLabel("Coverage", { exact: true }).click(); + await page.getByLabel('Coverage', { exact: true }).click(); // Wait for Vitest to have (re)started await page.waitForTimeout(2000); // Act - Open sidebar context menu and start focused test + await page.locator('[data-item-id="example-button--csf-3-primary"]').hover(); await page - .locator('[data-item-id="example-button--csf-3-primary"]') - .hover(); - await page - .locator( - '[data-item-id="example-button--csf-3-primary"] button[data-testid="context-menu"]' - ) + .locator('[data-item-id="example-button--csf-3-primary"] button[data-testid="context-menu"]') .click(); - const sidebarContextMenu = page.getByRole("dialog"); - await sidebarContextMenu.getByLabel("Start test run").click(); + const sidebarContextMenu = page.getByRole('dialog'); + await sidebarContextMenu.getByLabel('Start test run').click(); // HACK: the testing module popover has poor tracking of focus due to how many disabled // buttons it has and how deeply it changes its UI on events. This would be solved once // we move to a declarative menu, and there's an ongoing PR for that. Until then, we tab // around to reset focus. - await page.keyboard.press("Tab"); - await page.keyboard.press("Escape"); - await page.click("body"); + await page.keyboard.press('Tab'); + await page.keyboard.press('Escape'); + await page.click('body'); await expect(sidebarContextMenu).not.toBeVisible(); // Arrange - Wait for test to finish and unfocus sidebar context menu - await expect(page.locator("#testing-module-description")).toContainText( - "Ran 1 test", - { timeout: 30000 } - ); - await page.click("body"); + await expect(page.locator('#testing-module-description')).toContainText('Ran 1 test', { + timeout: 30000, + }); + await page.click('body'); // Assert - Coverage is not shown because Focused Tests shouldn't collect coverage - await expect(page.getByLabel("Open coverage report")).not.toBeVisible(); + await expect(page.getByLabel('Open coverage report')).not.toBeVisible(); // Act - Run ALL tests - await page.getByLabel("Start test run").click(); + await page.getByLabel('Start test run').click(); // Arrange - Wait for tests to finish - await expect(page.locator("#testing-module-description")).toContainText( - /Ran \d{2,} tests/, - { timeout: 30000 } - ); + await expect(page.locator('#testing-module-description')).toContainText(/Ran \d{2,} tests/, { + timeout: 30000, + }); // Assert - Coverage percentage is now collected and shown because running all tests automatically re-enables coverage - await expect(page.getByLabel("Open coverage report")).toBeVisible({ + await expect(page.getByLabel('Open coverage report')).toBeVisible({ timeout: 30000, }); - const sbPercentageText = await page - .getByLabel(/% coverage\)$/) - .textContent(); + const sbPercentageText = await page.getByLabel(/% coverage\)$/).textContent(); expect(sbPercentageText).toMatch(/^\d+%$/); - const sbPercentage = Number.parseInt( - sbPercentageText!.replace("%", "") ?? "" - ); + const sbPercentage = Number.parseInt(sbPercentageText!.replace('%', '') ?? ''); expect(sbPercentage).toBeGreaterThanOrEqual(0); expect(sbPercentage).toBeLessThanOrEqual(100); }); - test.fixme( - "should still collect statuses even when the browser is closed", - () => {} - ); + test.fixme('should still collect statuses even when the browser is closed', () => {}); - test.fixme( - "should have correct status count globally and in context menus", - () => {} - ); + test.fixme('should have correct status count globally and in context menus', () => {}); - test.fixme( - "should open the correct component test and a11y panels when clicking on statuses", - () => {} - ); + test.fixme('should open the correct component test and a11y panels when clicking on statuses', () => {}); }); diff --git a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/save-from-controls.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/save-from-controls.spec.ts index 0e093e00d614..2eea8ca989b2 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/save-from-controls.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/save-from-controls.spec.ts @@ -1,55 +1,42 @@ -import { expect, test } from "@playwright/test"; -import process from "process"; +import { expect, test } from '@playwright/test'; +import process from 'process'; -import { SbPage } from "../../../../code/e2e-tests/util"; +import { SbPage } from '../../../../code/e2e-tests/util'; -const STORYBOOK_URL = "http://localhost:6006"; -const type = process.env.STORYBOOK_TYPE || "dev"; +const STORYBOOK_URL = 'http://localhost:6006'; +const type = process.env.STORYBOOK_TYPE || 'dev'; -test.describe("save-from-controls", () => { - test.describe.configure({ mode: "serial" }); - test.skip( - type === "build", - `Skipping save-from-controls tests for production Storybooks` - ); +test.describe('save-from-controls', () => { + test.describe.configure({ mode: 'serial' }); + test.skip(type === 'build', `Skipping save-from-controls tests for production Storybooks`); - test("Should be able to update a story", async ({ page, browserName }) => { + test('Should be able to update a story', async ({ page, browserName }) => { // this is needed because the e2e test will generate a new file in the system // which we don't know of its location (it runs in different sandboxes) // so we just create a random id to make it easier to run tests const id = Math.random().toString(36).substring(7); - test.skip( - browserName !== "chromium", - `Skipping save-from-controls tests for ${browserName}` - ); + test.skip(browserName !== 'chromium', `Skipping save-from-controls tests for ${browserName}`); - await page.goto(STORYBOOK_URL + "/?path=/story/example-mybutton--primary"); + await page.goto(STORYBOOK_URL + '/?path=/story/example-mybutton--primary'); const sbPage = new SbPage(page, expect); await sbPage.waitUntilLoaded(); - await sbPage.viewAddonPanel("Controls"); + await sbPage.viewAddonPanel('Controls'); // Update an arg - const label = sbPage.panelContent().locator("textarea[name=children]"); + const label = sbPage.panelContent().locator('textarea[name=children]'); await label.fill(`"Updated ${id}"`); await label.blur(); // Assert the footer is shown - await sbPage - .panelContent() - .locator('[data-short-label="Unsaved changes"]') - .isVisible(); + await sbPage.panelContent().locator('[data-short-label="Unsaved changes"]').isVisible(); // update the story - await sbPage - .panelContent() - .locator("button") - .getByText("Update story") - .click(); + await sbPage.panelContent().locator('button').getByText('Update story').click(); // Assert the file is saved - const notification1 = sbPage.page.getByTitle("Story saved"); + const notification1 = sbPage.page.getByTitle('Story saved'); await expect(notification1).toBeVisible(); // dismiss @@ -62,10 +49,7 @@ test.describe("save-from-controls", () => { await label.blur(); // Assert the footer is shown - await sbPage - .panelContent() - .locator('[data-short-label="Unsaved changes"]') - .isVisible(); + await sbPage.panelContent().locator('[data-short-label="Unsaved changes"]').isVisible(); const buttons = sbPage .panelContent() @@ -74,13 +58,11 @@ test.describe("save-from-controls", () => { // clone the story await buttons.click(); - await sbPage.page - .getByPlaceholder("Story export name") - .fill("ClonedStory" + id); - await sbPage.page.getByRole("button", { exact: true, name: "Create" }).click(); + await sbPage.page.getByPlaceholder('Story export name').fill('ClonedStory' + id); + await sbPage.page.getByRole('button', { exact: true, name: 'Create' }).click(); // Assert the file is saved - const notification2 = sbPage.page.getByTitle("Story created"); + const notification2 = sbPage.page.getByTitle('Story created'); await expect(notification2).toBeVisible(); await notification2.click(); diff --git a/test-storybooks/portable-stories-kitchen-sink/react/playwright-e2e.config.ts b/test-storybooks/portable-stories-kitchen-sink/react/playwright-e2e.config.ts index c8971d86e642..68b036a9424f 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/playwright-e2e.config.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/playwright-e2e.config.ts @@ -1,12 +1,12 @@ -import { defineConfig, devices } from "@playwright/test"; -import path from "node:path"; +import { defineConfig, devices } from '@playwright/test'; +import path from 'node:path'; /** * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: "./e2e-tests", - outputDir: "./test-results", + testDir: './e2e-tests', + outputDir: './test-results', /* Maximum time one test can run for. */ timeout: (process.env.CI ? 60 : 30) * 1000, /* Run tests in files in parallel */ @@ -19,19 +19,19 @@ export default defineConfig({ workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: [ - ["line"], + ['line'], [ - "junit", + 'junit', { embedAnnotationsAsProperties: true, - outputFile: path.join(__dirname, "..", "..", "..", "test-results", "react-e2e-ui.xml"), + outputFile: path.join(__dirname, '..', '..', '..', 'test-results', 'react-e2e-ui.xml'), }, ], ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: "retain-on-failure", + trace: 'retain-on-failure', // video: "retain-on-failure", // headless: false, }, @@ -39,8 +39,8 @@ export default defineConfig({ /* Configure projects for major browsers */ projects: [ { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, }, // { // name: 'firefox', @@ -53,8 +53,8 @@ export default defineConfig({ ], webServer: { - command: "yarn storybook", - url: "http://127.0.0.1:6006", + command: 'yarn storybook', + url: 'http://127.0.0.1:6006', reuseExistingServer: true, }, }); diff --git a/test-storybooks/portable-stories-kitchen-sink/react/playwright/index.html b/test-storybooks/portable-stories-kitchen-sink/react/playwright/index.html index 155a309c4ac5..086a3ffa5bab 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/playwright/index.html +++ b/test-storybooks/portable-stories-kitchen-sink/react/playwright/index.html @@ -1,15 +1,13 @@ - + + + + + Testing Page + - - - - Testing Page - - - -
- - - - \ No newline at end of file + +
+ + + diff --git a/test-storybooks/portable-stories-kitchen-sink/react/playwright/index.ts b/test-storybooks/portable-stories-kitchen-sink/react/playwright/index.ts index dfacf75dfa9e..92522392e879 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/playwright/index.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/playwright/index.ts @@ -1,4 +1,4 @@ -import { setProjectAnnotations } from "@storybook/react-vite"; -import sbAnnotations from "../.storybook/preview"; +import { setProjectAnnotations } from '@storybook/react-vite'; +import sbAnnotations from '../.storybook/preview'; setProjectAnnotations([sbAnnotations]); diff --git a/test-storybooks/portable-stories-kitchen-sink/react/pre-e2e.js b/test-storybooks/portable-stories-kitchen-sink/react/pre-e2e.js index a684b479b9ce..f061802f88b1 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/pre-e2e.js +++ b/test-storybooks/portable-stories-kitchen-sink/react/pre-e2e.js @@ -1,10 +1,10 @@ /* eslint-disable no-undef */ /* eslint-disable @typescript-eslint/no-var-requires */ -const fs = require("node:fs"); -const path = require("node:path"); +const fs = require('node:fs'); +const path = require('node:path'); -const testStoryPath = path.resolve("stories/AddonTest.stories.tsx"); +const testStoryPath = path.resolve('stories/AddonTest.stories.tsx'); console.log(`Pre-e2e script: clearing ${testStoryPath}`); const storyContent = fs.readFileSync(testStoryPath).toString(); -fs.writeFileSync(testStoryPath, storyContent.replace("forceFailure: true", "forceFailure: false")); \ No newline at end of file +fs.writeFileSync(testStoryPath, storyContent.replace('forceFailure: true', 'forceFailure: false')); diff --git a/test-storybooks/portable-stories-kitchen-sink/react/stories/AddonTest.stories.tsx b/test-storybooks/portable-stories-kitchen-sink/react/stories/AddonTest.stories.tsx index fcb59a0ff94e..42e1be7a0495 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/stories/AddonTest.stories.tsx +++ b/test-storybooks/portable-stories-kitchen-sink/react/stories/AddonTest.stories.tsx @@ -17,9 +17,12 @@ export default meta; type Story = StoryObj; -const { pass } = instrument({ - pass: async () => {}, -}, { intercept: true }) +const { pass } = instrument( + { + pass: async () => {}, + }, + { intercept: true } +); export const ExpectedFailure: Story = { args: { @@ -27,16 +30,16 @@ export const ExpectedFailure: Story = { }, play: async ({ args }) => { await pass(); - if(args.forceFailure) { + if (args.forceFailure) { throw new Error('Expected failure'); } - } + }, }; export const ExpectedSuccess: Story = { play: async () => { await pass(); - } + }, }; export const ExpectedContent: Story = { @@ -45,7 +48,7 @@ export const ExpectedContent: Story = { await expect(button).toHaveTextContent('test'); const decoratorString = within(canvasElement).getByTestId('decorator-string'); await expect(decoratorString).toHaveTextContent('Global Decorator'); - } + }, }; export const LongRunning: Story = { @@ -56,17 +59,17 @@ export const LongRunning: Story = { export const MismatchFailure: Story = { play: async () => { await pass(); - if(!globalThis.__vitest_browser__) { + if (!globalThis.__vitest_browser__) { throw new Error('Expected failure'); } - } + }, }; // Tests will fail in browser, but pass in CLI export const MismatchSuccess: Story = { play: async () => { await pass(); - if(globalThis.__vitest_browser__) { + if (globalThis.__vitest_browser__) { throw new Error('Unexpected success'); } }, @@ -80,21 +83,21 @@ export const PreviewHeadTest: Story = { expect(styles.backgroundColor).toBe('rgb(250, 250, 210)'); // set in main.js#previewHead expect(styles.borderColor).toBe('rgb(255, 0, 0)'); - } + }, }; export const StaticDirTest: Story = { play: async () => { const path = '/test-static-dirs/static.js'; - const { staticFunction } = await import(/* @vite-ignore */path); + const { staticFunction } = await import(/* @vite-ignore */ path); expect(staticFunction()).toBe(true); - } -} + }, +}; export const ViteFinalTest: Story = { play: async () => { // @ts-expect-error TS doesn't know about the alias const { aliasedFunction } = await import('test-alias'); expect(aliasedFunction()).toBe(true); - } -} + }, +}; diff --git a/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.playwright.tsx b/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.playwright.tsx index 02dcc25df7b9..0210169335b5 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.playwright.tsx +++ b/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.playwright.tsx @@ -1,4 +1,3 @@ - import { createTest } from '@storybook/react/experimental-playwright'; import { test as base, expect } from '@playwright/experimental-ct-react'; import stories, { SingleComposedStory, WithSpanishGlobal } from './Button.stories.playwright'; @@ -18,14 +17,21 @@ test('renders with composeStory (singular)', async ({ mount }) => { }); test('renders story with props', async ({ mount }) => { - let called = false + let called = false; const component = await mount( - { called = true }}>child from test + { + called = true; + }} + > + child from test + ); await expect(component).toContainText('child from test'); await expect(component.getByRole('button')).toHaveClass(/storybook-button--primary/); - await component.getByRole('button').click() - await expect(called).toBe(true) + await component.getByRole('button').click(); + await expect(called).toBe(true); }); test('renders story with custom render', async ({ mount }) => { diff --git a/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.stories.playwright.ts b/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.stories.playwright.ts index e4a9a6ed24b2..bb4a7b194c76 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.stories.playwright.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.stories.playwright.ts @@ -1,15 +1,10 @@ -import { composeStories, composeStory } from "@storybook/react-vite"; -import * as stories from "./Button.stories"; +import { composeStories, composeStory } from '@storybook/react-vite'; +import * as stories from './Button.stories'; export default composeStories(stories); -export const SingleComposedStory = composeStory( - stories.CSF3Primary, - stories.default -); +export const SingleComposedStory = composeStory(stories.CSF3Primary, stories.default); -export const WithSpanishGlobal = composeStory( - stories.CSF2StoryWithLocale, - stories.default, - { initialGlobals: { locale: "es" } } -); +export const WithSpanishGlobal = composeStory(stories.CSF2StoryWithLocale, stories.default, { + initialGlobals: { locale: 'es' }, +}); diff --git a/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.stories.tsx b/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.stories.tsx index c40e163b0d32..1ca5894e791f 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.stories.tsx +++ b/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.stories.tsx @@ -63,7 +63,7 @@ CSF2StoryWithParamsAndDecorator.decorators = [(StoryFn) => ]; export const CSF3Primary: CSF3Story = { args: { - children: "foo", + children: 'foo', size: 'large', primary: true, }, @@ -110,4 +110,4 @@ export const WithLoader: CSF3Story<{ mockFn: (val: string) => string }> = { play: async () => { expect(mockFn).toHaveBeenCalledWith('render'); }, -}; \ No newline at end of file +}; diff --git a/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.tsx b/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.tsx index 8f5d125bb293..58dc199388ec 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.tsx +++ b/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.tsx @@ -30,7 +30,7 @@ export interface ButtonProps { export const Button: React.FC = (props) => { const { primary = false, size = 'medium', backgroundColor, children, ...otherProps } = props; const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary'; - console.log({props}) + console.log({ props }); return ( +const Component = () => ; const meta = { title: 'Addons/Group/Other', @@ -11,8 +11,7 @@ export default meta; type Story = StoryObj; -export const Passes: Story = { -}; +export const Passes: Story = {}; export const Fails: Story = { play: async () => { diff --git a/test-storybooks/portable-stories-kitchen-sink/react/stories/get-button-string.ts b/test-storybooks/portable-stories-kitchen-sink/react/stories/get-button-string.ts index 7c9d9ab153d0..c6a83a8018de 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/stories/get-button-string.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/stories/get-button-string.ts @@ -1,3 +1,3 @@ export const getButtonString = () => { - return "test"; + return 'test'; }; diff --git a/test-storybooks/portable-stories-kitchen-sink/react/vite.config.mts b/test-storybooks/portable-stories-kitchen-sink/react/vite.config.mts index 1169d7fd3d6d..1888bf1b4069 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/vite.config.mts +++ b/test-storybooks/portable-stories-kitchen-sink/react/vite.config.mts @@ -1,7 +1,7 @@ -import { defineConfig } from "vitest/config"; -import react from "@vitejs/plugin-react"; -import { storybookTest } from "@storybook/addon-vitest/vitest-plugin"; -import { playwright } from "@vitest/browser-playwright"; +import { defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react'; +import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; +import { playwright } from '@vitest/browser-playwright'; // https://vitejs.dev/config/ export default defineConfig({ @@ -11,15 +11,15 @@ export default defineConfig({ process.env.SKIP_FAIL_ON_PURPOSE ? { tags: { - exclude: ["fail-on-purpose"], + exclude: ['fail-on-purpose'], }, } : undefined ), ], test: { - name: "storybook", - pool: "threads", + name: 'storybook', + pool: 'threads', deps: { optimizer: { web: { @@ -33,11 +33,11 @@ export default defineConfig({ headless: true, instances: [ { - browser: "chromium", + browser: 'chromium', }, ], }, - setupFiles: ["./.storybook/vitest.setup.ts"], - environment: "jsdom", + setupFiles: ['./.storybook/vitest.setup.ts'], + environment: 'jsdom', }, }); diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/.storybook/main.ts b/test-storybooks/portable-stories-kitchen-sink/svelte/.storybook/main.ts index 9647d21bb06c..36b458fdc364 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/.storybook/main.ts +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/.storybook/main.ts @@ -1,7 +1,7 @@ import type { StorybookConfig } from '@storybook/svelte-vite'; const config: StorybookConfig = { - stories: ["../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + stories: ['../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], framework: { name: '@storybook/svelte-vite', options: {}, diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/.storybook/preview.ts b/test-storybooks/portable-stories-kitchen-sink/svelte/.storybook/preview.ts index 39d847a4b2a7..75d5ad59fdfd 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/.storybook/preview.ts +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/.storybook/preview.ts @@ -1,15 +1,13 @@ import type { Preview } from '@storybook/svelte'; -import GlobalDecorator from './GlobalDecorator.svelte' +import GlobalDecorator from './GlobalDecorator.svelte'; console.log('preview file is called!'); const preview: Preview = { decorators: [ - () => ( - { - Component: GlobalDecorator, - } - ), + () => ({ + Component: GlobalDecorator, + }), ], globalTypes: { locale: { diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/cypress.config.ts b/test-storybooks/portable-stories-kitchen-sink/svelte/cypress.config.ts index 173df08ceb54..76ce5a7a053a 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/cypress.config.ts +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/cypress.config.ts @@ -1,10 +1,10 @@ -import { defineConfig } from "cypress"; +import { defineConfig } from 'cypress'; export default defineConfig({ component: { devServer: { - framework: "svelte", - bundler: "vite", + framework: 'svelte', + bundler: 'vite', }, }, }); diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/cypress/support/commands.ts b/test-storybooks/portable-stories-kitchen-sink/svelte/cypress/support/commands.ts index 698b01a42c35..95857aea4cdf 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/cypress/support/commands.ts +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/cypress/support/commands.ts @@ -34,4 +34,4 @@ // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable // } // } -// } \ No newline at end of file +// } diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/cypress/support/component-index.html b/test-storybooks/portable-stories-kitchen-sink/svelte/cypress/support/component-index.html index ac6e79fd83df..faf3b5f43b10 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/cypress/support/component-index.html +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/cypress/support/component-index.html @@ -1,12 +1,12 @@ - + - - - + + + Components App
- \ No newline at end of file + diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/cypress/support/component.ts b/test-storybooks/portable-stories-kitchen-sink/svelte/cypress/support/component.ts index 487211bf402e..26c5222edf6c 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/cypress/support/component.ts +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/cypress/support/component.ts @@ -14,12 +14,12 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import './commands'; // Alternatively you can use CommonJS syntax: // require('./commands') -import { mount } from 'cypress/svelte' +import { mount } from 'cypress/svelte'; import type { ProjectAnnotations } from 'storybook/internal/types'; import type { SvelteRenderer } from '@storybook/svelte'; @@ -33,12 +33,12 @@ import sbAnnotations from '../../.storybook/preview'; declare global { namespace Cypress { interface Chainable { - mount: typeof mount + mount: typeof mount; } } } -Cypress.Commands.add('mount', mount) +Cypress.Commands.add('mount', mount); // Example use: // cy.mount(MyComponent) @@ -49,6 +49,4 @@ Cypress.Commands.add('mount', mount) // which will break process.env = {}; -setProjectAnnotations([ - sbAnnotations, -]); +setProjectAnnotations([sbAnnotations]); diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/playwright.config.ts b/test-storybooks/portable-stories-kitchen-sink/svelte/playwright.config.ts index cb08be01114d..e4f340927d96 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/playwright.config.ts +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/playwright.config.ts @@ -29,7 +29,7 @@ export default defineConfig({ /* Port to use for Playwright component endpoint. */ ctPort: 3100, - ctViteConfig + ctViteConfig, }, /* Configure projects for major browsers */ diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/playwright/index.html b/test-storybooks/portable-stories-kitchen-sink/svelte/playwright/index.html index 000deead368d..56da0401ae84 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/playwright/index.html +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/playwright/index.html @@ -1,4 +1,4 @@ - + diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/playwright/index.ts b/test-storybooks/portable-stories-kitchen-sink/svelte/playwright/index.ts index 50dbc12fb061..85d255b09b2b 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/playwright/index.ts +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/playwright/index.ts @@ -1,6 +1,4 @@ -import { setProjectAnnotations } from '@storybook/svelte' -import sbAnnotations from '../.storybook/preview' +import { setProjectAnnotations } from '@storybook/svelte'; +import sbAnnotations from '../.storybook/preview'; -setProjectAnnotations([ - sbAnnotations, -]); +setProjectAnnotations([sbAnnotations]); diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.cy.tsx b/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.cy.tsx index fdb912381a4f..6126f8503285 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.cy.tsx +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.cy.tsx @@ -9,10 +9,10 @@ describe(' { // TODO figure out the following issue ReferenceError: $state is not defined // at createSvelte5Props (http://localhost:5173/__cypress/src/@fs/storybook/code/renderers/svelte/dist/createSvelte5Props.svelte.js:11:17) const Primary = composeStory(stories.CSF3Primary, stories.default); - cy.mount(CSF3Primary) + cy.mount(CSF3Primary); cy.get('button').should('contain.text', 'foo'); // cy.get('[data-decorator]').should('exist'); - }) + }); // it('renders primary button with custom args', async () => { // cy.mount(CSF3Primary({ label: 'bar' })) @@ -42,4 +42,4 @@ describe(' { // cy.get('[data-testid="input"]').should('contain.value', 'Hello world!'); // }); // }) -}) +}); diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.playwright.ts b/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.playwright.ts index a9d9f32e3d26..23a0c78d1be1 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.playwright.ts +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.playwright.ts @@ -7,5 +7,5 @@ const test = createTest(base); test.skip('renders primary button', async ({ mount }) => { // TODO: this is not working, probably the translation that Playwright does not work with portable stories yet - await mount(stories.WithLoader); + await mount(stories.WithLoader); }); diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.stories.playwright.tsx b/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.stories.playwright.tsx index 643a88617b54..6a71e4a54016 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.stories.playwright.tsx +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.stories.playwright.tsx @@ -1,4 +1,4 @@ import { composeStories } from '@storybook/svelte'; import * as stories from './Button.stories'; -export default composeStories(stories); \ No newline at end of file +export default composeStories(stories); diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/svelte.config.js b/test-storybooks/portable-stories-kitchen-sink/svelte/svelte.config.js index b0683fd24d70..3bce8eaa6cda 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/svelte.config.js +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/svelte.config.js @@ -1,7 +1,7 @@ -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; export default { // Consult https://svelte.dev/docs#compile-time-svelte-preprocess // for more information about preprocessors preprocess: vitePreprocess(), -} +}; diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/vite.config.ts b/test-storybooks/portable-stories-kitchen-sink/svelte/vite.config.ts index dbb6ac010800..0e2accc934a9 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/vite.config.ts +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/vite.config.ts @@ -1,11 +1,11 @@ -import { defineConfig } from 'vitest/config' -import { svelte } from '@sveltejs/vite-plugin-svelte' -import { svelteTesting } from '@testing-library/svelte/vite' +import { defineConfig } from 'vitest/config'; +import { svelte } from '@sveltejs/vite-plugin-svelte'; +import { svelteTesting } from '@testing-library/svelte/vite'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [svelte(), svelteTesting()], test: { - environment: 'happy-dom' - } -}) + environment: 'happy-dom', + }, +}); diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/.eslintrc.cjs b/test-storybooks/portable-stories-kitchen-sink/vue3/.eslintrc.cjs index 29cb6d5a0877..f51e2baf0b82 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/.eslintrc.cjs +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/.eslintrc.cjs @@ -1,14 +1,16 @@ module.exports = { root: true, env: { browser: true, es2020: true }, - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + 'plugin:storybook/recommended', + ], ignorePatterns: ['dist', '.eslintrc.cjs'], parser: '@typescript-eslint/parser', plugins: ['react-refresh'], rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], }, -} +}; diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/.storybook/main.ts b/test-storybooks/portable-stories-kitchen-sink/vue3/.storybook/main.ts index 9e904aadd91a..a51abbb488b3 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/.storybook/main.ts +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/.storybook/main.ts @@ -1,9 +1,9 @@ -import type { StorybookConfig } from "@storybook/vue3-vite"; +import type { StorybookConfig } from '@storybook/vue3-vite'; const config: StorybookConfig = { - stories: ["../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + stories: ['../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], framework: { - name: "@storybook/vue3-vite", + name: '@storybook/vue3-vite', options: {}, }, }; diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/.storybook/preview.ts b/test-storybooks/portable-stories-kitchen-sink/vue3/.storybook/preview.ts index 7275c5ccc63a..be05e5472568 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/.storybook/preview.ts +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/.storybook/preview.ts @@ -5,15 +5,15 @@ console.log('preview file is called!'); const preview: Preview = { // TODO: figure out decorators decorators: [ - () => ({ + () => ({ template: `
Global Decorator
- ` - }) + `, + }), ], globalTypes: { locale: { diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/cypress.config.ts b/test-storybooks/portable-stories-kitchen-sink/vue3/cypress.config.ts index 5c795e604b59..8befed21e087 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/cypress.config.ts +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/cypress.config.ts @@ -1,11 +1,11 @@ -import { defineConfig } from "cypress"; +import { defineConfig } from 'cypress'; export default defineConfig({ screenshotOnRunFailure: false, component: { devServer: { - framework: "vue", - bundler: "vite", + framework: 'vue', + bundler: 'vite', }, }, }); diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/cypress/support/commands.ts b/test-storybooks/portable-stories-kitchen-sink/vue3/cypress/support/commands.ts index 2ebd0f10df14..50fc1ebf5c5a 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/cypress/support/commands.ts +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/cypress/support/commands.ts @@ -7,4 +7,4 @@ // For more comprehensive examples of custom // commands please read more here: // https://on.cypress.io/custom-commands -// *********************************************** \ No newline at end of file +// *********************************************** diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/cypress/support/component-index.html b/test-storybooks/portable-stories-kitchen-sink/vue3/cypress/support/component-index.html index ac6e79fd83df..faf3b5f43b10 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/cypress/support/component-index.html +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/cypress/support/component-index.html @@ -1,12 +1,12 @@ - + - - - + + + Components App
- \ No newline at end of file + diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/cypress/support/component.ts b/test-storybooks/portable-stories-kitchen-sink/vue3/cypress/support/component.ts index 5a20af7e363b..dacd2352deed 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/cypress/support/component.ts +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/cypress/support/component.ts @@ -15,15 +15,14 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import './commands'; -import { mount } from 'cypress/vue' +import { mount } from 'cypress/vue'; import type { ProjectAnnotations } from 'storybook/internal/types'; import { VueRenderer, setProjectAnnotations } from '@storybook/vue3'; import sbAnnotations from '../../.storybook/preview'; - // Augment the Cypress namespace to include type definitions for // your custom command. // Alternatively, can be defined in cypress/support/component.d.ts @@ -31,12 +30,12 @@ import sbAnnotations from '../../.storybook/preview'; declare global { namespace Cypress { interface Chainable { - mount: typeof mount + mount: typeof mount; } } } -Cypress.Commands.add('mount', mount) +Cypress.Commands.add('mount', mount); // This is needed because Cypress defines process but not process.env // And if the play function fails, testing library's internals have a check @@ -44,6 +43,4 @@ Cypress.Commands.add('mount', mount) // which will break process.env = {}; -setProjectAnnotations([ - sbAnnotations, -]); +setProjectAnnotations([sbAnnotations]); diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/playwright.config.ts b/test-storybooks/portable-stories-kitchen-sink/vue3/playwright.config.ts index 83c10ea7ac90..4ef6aaf46648 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/playwright.config.ts +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/playwright.config.ts @@ -34,7 +34,7 @@ export default defineConfig({ vue: 'vue/dist/vue.esm-bundler.js', }, }, - } + }, }, /* Configure projects for major browsers */ diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/playwright/index.html b/test-storybooks/portable-stories-kitchen-sink/vue3/playwright/index.html index 155a309c4ac5..086a3ffa5bab 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/playwright/index.html +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/playwright/index.html @@ -1,15 +1,13 @@ - + + + + + Testing Page + - - - - Testing Page - - - -
- - - - \ No newline at end of file + +
+ + + diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/playwright/index.ts b/test-storybooks/portable-stories-kitchen-sink/vue3/playwright/index.ts index 379cc9ea3811..7374954469c1 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/playwright/index.ts +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/playwright/index.ts @@ -1,6 +1,4 @@ -import { setProjectAnnotations } from '@storybook/vue3' -import sbAnnotations from '../.storybook/preview' +import { setProjectAnnotations } from '@storybook/vue3'; +import sbAnnotations from '../.storybook/preview'; -setProjectAnnotations([ - sbAnnotations, -]); +setProjectAnnotations([sbAnnotations]); diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.cy.tsx b/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.cy.tsx index 03f74c5bef3f..489ea92b71be 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.cy.tsx +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.cy.tsx @@ -2,41 +2,45 @@ import * as stories from './Button.stories'; import { composeStories } from '@storybook/vue3'; -const { CSF3Primary, WithLoader, CSF3InputFieldFilled } = composeStories(stories) +const { CSF3Primary, WithLoader, CSF3InputFieldFilled } = composeStories(stories); describe(' { it('renders primary button', async () => { - cy.mount(CSF3Primary()) + cy.mount(CSF3Primary()); cy.get('button').should('contain.text', 'foo'); cy.get('[data-decorator]').should('exist'); - }) + }); it('renders primary button with custom args', async () => { - cy.mount(CSF3Primary({ label: 'bar' })) + cy.mount(CSF3Primary({ label: 'bar' })); cy.get('button').should('contain.text', 'bar'); - }) + }); it.skip('renders with loaders and play function', () => { - cy.then(async() => { + cy.then(async () => { await WithLoader.load(); }); cy.mount(WithLoader()); - cy.then(async() => { - await WithLoader.play!({ canvasElement: document.querySelector('[data-cy-root]') as HTMLElement }); + cy.then(async () => { + await WithLoader.play!({ + canvasElement: document.querySelector('[data-cy-root]') as HTMLElement, + }); }); cy.get('[data-testid="loaded-data"]').should('contain.text', 'bar'); cy.get('[data-testid="mock-data"]').should('contain.text', 'mockFn return value'); - }) + }); it.skip('renders with play function', () => { cy.mount(CSF3InputFieldFilled()); - cy.then(async() => { - await CSF3InputFieldFilled.play!({ canvasElement: document.querySelector('[data-cy-root]') as HTMLElement }); + cy.then(async () => { + await CSF3InputFieldFilled.play!({ + canvasElement: document.querySelector('[data-cy-root]') as HTMLElement, + }); cy.get('[data-testid="input"]').should('contain.value', 'Hello world!'); }); - }) -}) + }); +}); diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.playwright.tsx b/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.playwright.tsx index 6251fcc24006..e89412c9c5bd 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.playwright.tsx +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.playwright.tsx @@ -24,7 +24,9 @@ test('renders story with props as second argument', async ({ mount }) => { test('renders story with custom render', async ({ mount }) => { const component = await mount(); - await expect(component.getByTestId('custom-render')).toContainText('I am a custom render function'); + await expect(component.getByTestId('custom-render')).toContainText( + 'I am a custom render function' + ); await expect(component.getByRole('button')).toHaveText('foo'); }); diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.stories.portable.ts b/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.stories.portable.ts index 5ae5a5638298..afc7b3f85ed5 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.stories.portable.ts +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.stories.portable.ts @@ -5,4 +5,6 @@ export default composeStories(stories); export const SingleComposedStory = composeStory(stories.CSF3Primary, stories.default); -export const WithSpanishGlobal = composeStory(stories.CSF2StoryWithLocale, stories.default, {initialGlobals: { locale: 'es' }}); +export const WithSpanishGlobal = composeStory(stories.CSF2StoryWithLocale, stories.default, { + initialGlobals: { locale: 'es' }, +}); diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.stories.ts b/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.stories.ts index 7b4fb9e96863..d537276b9525 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.stories.ts +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/stories/Button.stories.ts @@ -93,7 +93,7 @@ export const CSF3ButtonWithRender: CSF3Story = { render: (args) => ({ components: { Button }, setup() { - console.log('hello') + console.log('hello'); return { args }; }, template: ` @@ -153,4 +153,3 @@ export const WithLoader: StoryObj<{ mockFn: (val: string) => string }> = { expect(mockFn).toHaveBeenCalledWith('render'); }, }; - diff --git a/test-storybooks/portable-stories-kitchen-sink/vue3/vite.config.ts b/test-storybooks/portable-stories-kitchen-sink/vue3/vite.config.ts index 05c17402a4a9..6405595ab23d 100644 --- a/test-storybooks/portable-stories-kitchen-sink/vue3/vite.config.ts +++ b/test-storybooks/portable-stories-kitchen-sink/vue3/vite.config.ts @@ -1,7 +1,7 @@ -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue()], -}) +}); diff --git a/test-storybooks/server-kitchen-sink/.storybook/main.ts b/test-storybooks/server-kitchen-sink/.storybook/main.ts index eb4d8a8f9d9a..75f32283241e 100644 --- a/test-storybooks/server-kitchen-sink/.storybook/main.ts +++ b/test-storybooks/server-kitchen-sink/.storybook/main.ts @@ -3,11 +3,7 @@ import type { StorybookConfig } from '@storybook/server-webpack5'; const mainConfig: StorybookConfig = { stories: ['../stories/**/*.stories.@(json|yaml|yml)'], logLevel: 'debug', - addons: [ - '@storybook/addon-docs', - '@storybook/addon-a11y', - '@storybook/addon-links' - ], + addons: ['@storybook/addon-docs', '@storybook/addon-a11y', '@storybook/addon-links'], core: { disableTelemetry: true, }, diff --git a/test-storybooks/standalone-preview/storybook.html b/test-storybooks/standalone-preview/storybook.html index 8982f5f79f68..4de612cf1957 100644 --- a/test-storybooks/standalone-preview/storybook.html +++ b/test-storybooks/standalone-preview/storybook.html @@ -1,4 +1,4 @@ - + diff --git a/test-storybooks/yarn-pnp/.storybook/main.ts b/test-storybooks/yarn-pnp/.storybook/main.ts index 88cd184ea953..1fa78064b631 100644 --- a/test-storybooks/yarn-pnp/.storybook/main.ts +++ b/test-storybooks/yarn-pnp/.storybook/main.ts @@ -1,15 +1,15 @@ -import type { StorybookConfig } from "@storybook/vue3-vite"; +import type { StorybookConfig } from '@storybook/vue3-vite'; const config: StorybookConfig = { - stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], addons: [ - "@chromatic-com/storybook", - "@storybook/addon-docs", - "@storybook/addon-a11y", - "@storybook/addon-vitest", + '@chromatic-com/storybook', + '@storybook/addon-docs', + '@storybook/addon-a11y', + '@storybook/addon-vitest', ], framework: { - name: "@storybook/vue3-vite", + name: '@storybook/vue3-vite', options: {}, }, }; diff --git a/test-storybooks/yarn-pnp/.storybook/preview.ts b/test-storybooks/yarn-pnp/.storybook/preview.ts index 073582ec0393..9017263ad9fc 100644 --- a/test-storybooks/yarn-pnp/.storybook/preview.ts +++ b/test-storybooks/yarn-pnp/.storybook/preview.ts @@ -1,11 +1,11 @@ -import type { Preview } from '@storybook/react-vite' +import type { Preview } from '@storybook/react-vite'; const preview: Preview = { parameters: { controls: { matchers: { - color: /(background|color)$/i, - date: /Date$/i, + color: /(background|color)$/i, + date: /Date$/i, }, }, @@ -13,9 +13,9 @@ const preview: Preview = { // 'todo' - show a11y violations in the test UI only // 'error' - fail CI on a11y violations // 'off' - skip a11y checks entirely - test: 'todo' - } + test: 'todo', + }, }, }; -export default preview; \ No newline at end of file +export default preview; diff --git a/test-storybooks/yarn-pnp/eslint.config.js b/test-storybooks/yarn-pnp/eslint.config.js index 279fc495572d..8d5ac6aa8bb1 100644 --- a/test-storybooks/yarn-pnp/eslint.config.js +++ b/test-storybooks/yarn-pnp/eslint.config.js @@ -1,22 +1,22 @@ // For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format -import storybook from "eslint-plugin-storybook"; +import storybook from 'eslint-plugin-storybook'; -import js from '@eslint/js' -import globals from 'globals' -import tseslint from 'typescript-eslint' -import { globalIgnores } from 'eslint/config' +import js from '@eslint/js'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; +import { globalIgnores } from 'eslint/config'; -export default tseslint.config([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - js.configs.recommended, - tseslint.configs.recommended, - ], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, +export default tseslint.config( + [ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [js.configs.recommended, tseslint.configs.recommended], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, }, - }, -], storybook.configs["flat/recommended"]); + ], + storybook.configs['flat/recommended'] +); diff --git a/test-storybooks/yarn-pnp/index.html b/test-storybooks/yarn-pnp/index.html index fc7120dd11e1..c28e2a40aee5 100644 --- a/test-storybooks/yarn-pnp/index.html +++ b/test-storybooks/yarn-pnp/index.html @@ -1,16 +1,14 @@ + + + + + vue3-vite + - - - - - vue3-vite - - - -
- - - - \ No newline at end of file + +
+ + + diff --git a/test-storybooks/yarn-pnp/package.json b/test-storybooks/yarn-pnp/package.json index 2f935ee231e6..41ba732cbab6 100644 --- a/test-storybooks/yarn-pnp/package.json +++ b/test-storybooks/yarn-pnp/package.json @@ -76,4 +76,4 @@ "nx": { "includedScripts": [] } -} \ No newline at end of file +} diff --git a/test-storybooks/yarn-pnp/src/App.vue b/test-storybooks/yarn-pnp/src/App.vue index 58b0f21b1647..8c9655e9035e 100644 --- a/test-storybooks/yarn-pnp/src/App.vue +++ b/test-storybooks/yarn-pnp/src/App.vue @@ -1,5 +1,5 @@