),
diff --git a/code/renderers/server/package.json b/code/renderers/server/package.json
index 3ed4433e7755..a3ea2c4d152c 100644
--- a/code/renderers/server/package.json
+++ b/code/renderers/server/package.json
@@ -46,7 +46,7 @@
"*.d.ts"
],
"scripts": {
- "check": "tsc --noEmit",
+ "check": "../../../scripts/node_modules/.bin/tsc --noEmit",
"prep": "../../../scripts/prepare/bundle.ts"
},
"dependencies": {
diff --git a/code/renderers/svelte/package.json b/code/renderers/svelte/package.json
index b44ac2169e60..11248120f1a7 100644
--- a/code/renderers/svelte/package.json
+++ b/code/renderers/svelte/package.json
@@ -50,7 +50,7 @@
"*.d.ts"
],
"scripts": {
- "check": "tsc --noEmit",
+ "check": "../../../scripts/node_modules/.bin/tsc --noEmit",
"prep": "../../../scripts/prepare/bundle.ts"
},
"dependencies": {
diff --git a/code/renderers/svelte/template/stories/README.md b/code/renderers/svelte/template/stories/README.md
new file mode 100644
index 000000000000..37d8743baec7
--- /dev/null
+++ b/code/renderers/svelte/template/stories/README.md
@@ -0,0 +1 @@
+Placeholder until we write some render-specific stories
diff --git a/code/renderers/vue/package.json b/code/renderers/vue/package.json
index bbd7df617dc9..aa90f7d9f44e 100644
--- a/code/renderers/vue/package.json
+++ b/code/renderers/vue/package.json
@@ -46,7 +46,7 @@
"*.d.ts"
],
"scripts": {
- "check": "tsc --noEmit",
+ "check": "../../../scripts/node_modules/.bin/tsc --noEmit",
"prep": "../../../scripts/prepare/bundle.ts"
},
"dependencies": {
diff --git a/code/renderers/vue3/package.json b/code/renderers/vue3/package.json
index 01c271e5b08c..8454d6d72942 100644
--- a/code/renderers/vue3/package.json
+++ b/code/renderers/vue3/package.json
@@ -46,7 +46,7 @@
"*.d.ts"
],
"scripts": {
- "check": "tsc --noEmit",
+ "check": "../../../scripts/node_modules/.bin/tsc --noEmit",
"prep": "../../../scripts/prepare/bundle.ts"
},
"dependencies": {
diff --git a/code/renderers/vue3/template/stories/README.md b/code/renderers/vue3/template/stories/README.md
new file mode 100644
index 000000000000..37d8743baec7
--- /dev/null
+++ b/code/renderers/vue3/template/stories/README.md
@@ -0,0 +1 @@
+Placeholder until we write some render-specific stories
diff --git a/code/renderers/web-components/package.json b/code/renderers/web-components/package.json
index b66fff4294aa..1cc0768d77af 100644
--- a/code/renderers/web-components/package.json
+++ b/code/renderers/web-components/package.json
@@ -48,7 +48,7 @@
"*.d.ts"
],
"scripts": {
- "check": "tsc --noEmit",
+ "check": "../../../scripts/node_modules/.bin/tsc --noEmit",
"prep": "../../../scripts/prepare/bundle.ts"
},
"dependencies": {
diff --git a/code/workspace.json b/code/workspace.json
index da1afc7921a1..a08ed1c6f24a 100644
--- a/code/workspace.json
+++ b/code/workspace.json
@@ -411,11 +411,6 @@
"type": "library",
"implicitDependencies": []
},
- "cra-kitchen-sink": {
- "root": "examples/cra-kitchen-sink",
- "type": "library",
- "implicitDependencies": []
- },
"cra-ts-kitchen-sink": {
"root": "examples/cra-ts-kitchen-sink",
"type": "library",
diff --git a/code/yarn.lock b/code/yarn.lock
index a36a8805ad67..a947e264336b 100644
--- a/code/yarn.lock
+++ b/code/yarn.lock
@@ -17458,37 +17458,6 @@ __metadata:
languageName: node
linkType: hard
-"cra-kitchen-sink@workspace:examples/cra-kitchen-sink":
- version: 0.0.0-use.local
- resolution: "cra-kitchen-sink@workspace:examples/cra-kitchen-sink"
- dependencies:
- "@pmmmwh/react-refresh-webpack-plugin": ^0.5.5
- "@storybook/addon-a11y": 7.0.0-alpha.33
- "@storybook/addon-actions": 7.0.0-alpha.33
- "@storybook/addon-backgrounds": 7.0.0-alpha.33
- "@storybook/addon-docs": 7.0.0-alpha.33
- "@storybook/addon-highlight": 7.0.0-alpha.33
- "@storybook/addon-jest": 7.0.0-alpha.33
- "@storybook/addon-links": 7.0.0-alpha.33
- "@storybook/addon-storyshots": 7.0.0-alpha.33
- "@storybook/addons": 7.0.0-alpha.33
- "@storybook/builder-webpack5": 7.0.0-alpha.33
- "@storybook/client-logger": 7.0.0-alpha.33
- "@storybook/preset-create-react-app": ^4.1.0
- "@storybook/react": 7.0.0-alpha.33
- "@storybook/react-webpack5": 7.0.0-alpha.33
- "@storybook/theming": 7.0.0-alpha.33
- global: ^4.4.0
- prop-types: ^15.7.2
- react: 16.14.0
- react-dom: 16.14.0
- react-lifecycles-compat: ^3.0.4
- react-scripts: ^5.0.1
- storybook: 7.0.0-alpha.33
- webpack: 5
- languageName: unknown
- linkType: soft
-
"cra-ts-kitchen-sink@workspace:examples/cra-ts-kitchen-sink":
version: 0.0.0-use.local
resolution: "cra-ts-kitchen-sink@workspace:examples/cra-ts-kitchen-sink"
@@ -37452,7 +37421,7 @@ __metadata:
languageName: node
linkType: hard
-"react-lifecycles-compat@npm:^3.0.0, react-lifecycles-compat@npm:^3.0.4":
+"react-lifecycles-compat@npm:^3.0.0":
version: 3.0.4
resolution: "react-lifecycles-compat@npm:3.0.4"
checksum: 1d0df3c85af79df720524780f00c064d53a9dd1899d785eddb7264b378026979acbddb58a4b7e06e7d0d12aa1494fd5754562ee55d32907b15601068dae82c27
diff --git a/docs/snippets/angular/my-component-play-function-alt-queries.mdx.mdx b/docs/snippets/angular/my-component-play-function-alt-queries.mdx.mdx
index 546d391f02db..0e7fd887e118 100644
--- a/docs/snippets/angular/my-component-play-function-alt-queries.mdx.mdx
+++ b/docs/snippets/angular/my-component-play-function-alt-queries.mdx.mdx
@@ -17,20 +17,11 @@ import { MyComponent } from './MyComponent.component';
{
+ play={async () => {
// See https://storybook.js.org/docs/7.0/angular/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
await userEvent.click(screen.getByRole('button', { name: / button label/i }));
}}
render={(args) => ({
props: args,
})} />
- {
- // The play function interacts with the component and looks for the text
- await screen.findByText('example string');
- }}
- render={(args) => ({
- props: args,
- })} />
```
\ No newline at end of file
diff --git a/docs/snippets/angular/my-component-play-function-alt-queries.ts.mdx b/docs/snippets/angular/my-component-play-function-alt-queries.ts.mdx
index 2a0ee07f7cfc..36f5458a3e27 100644
--- a/docs/snippets/angular/my-component-play-function-alt-queries.ts.mdx
+++ b/docs/snippets/angular/my-component-play-function-alt-queries.ts.mdx
@@ -1,7 +1,7 @@
```ts
// MyComponent.stories.ts
-import type { Meta, Story } from '@storybook/addon-docs';
+import type { Meta, Story } from '@storybook/angular';
import { screen, userEvent } from '@storybook/testing-library';
@@ -22,11 +22,4 @@ export const ExampleWithRole: Story = {
await userEvent.click(screen.getByRole('button', { name: / button label/i }));
},
};
-
-export const ExampleWithText: Story = {
- play: async () => {
- // The play function interacts with the component and looks for the text
- await screen.findByText('example string');
- },
-};
```
\ No newline at end of file
diff --git a/docs/snippets/angular/my-component-play-function-query-findby.mdx.mdx b/docs/snippets/angular/my-component-play-function-query-findby.mdx.mdx
new file mode 100644
index 000000000000..cef66ae17b66
--- /dev/null
+++ b/docs/snippets/angular/my-component-play-function-query-findby.mdx.mdx
@@ -0,0 +1,29 @@
+```md
+
+
+import { Meta, Story }Β from '@storybook/addon-docs';
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import { MyComponent } from './MyComponent.component';
+
+
+
+
+
+ {
+ // Other steps
+
+ // Waits for the component to be rendered before querying the element
+ await screen.findByRole('button', { name: / button label/i }));
+ }}
+ render={(args) => ({
+ props: args,
+ })} />
+```
\ No newline at end of file
diff --git a/docs/snippets/angular/my-component-play-function-query-findby.ts.mdx b/docs/snippets/angular/my-component-play-function-query-findby.ts.mdx
new file mode 100644
index 000000000000..ba9229b5c11e
--- /dev/null
+++ b/docs/snippets/angular/my-component-play-function-query-findby.ts.mdx
@@ -0,0 +1,27 @@
+```ts
+// MyComponent.stories.ts
+
+import type { Meta, Story } from '@storybook/angular';
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import { MyComponent } from './MyComponent.component';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/7.0/angular/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'Async Query Methods',
+ component: MyComponent,
+} as Meta;
+
+export const AsyncExample: Story = {
+ play: async () => {
+ // Other steps
+
+ // Waits for the component to be rendered before querying the element
+ await screen.findByRole('button', { name: / button label/i }));
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/react/my-component-play-function-alt-queries.js.mdx b/docs/snippets/react/my-component-play-function-alt-queries.js.mdx
index 2b1101f8e042..8017824f22fa 100644
--- a/docs/snippets/react/my-component-play-function-alt-queries.js.mdx
+++ b/docs/snippets/react/my-component-play-function-alt-queries.js.mdx
@@ -20,11 +20,4 @@ export const ExampleWithRole = {
await userEvent.click(screen.getByRole('button', { name: / button label/i }));
},
};
-
-export const ExampleWithText = {
- play: async () => {
- // The play function interacts with the component and looks for the text
- await screen.findByText('example string');
- },
-};
```
\ No newline at end of file
diff --git a/docs/snippets/react/my-component-play-function-alt-queries.mdx.mdx b/docs/snippets/react/my-component-play-function-alt-queries.mdx.mdx
index 8ece56cb44a1..c4b5cf2f9d1e 100644
--- a/docs/snippets/react/my-component-play-function-alt-queries.mdx.mdx
+++ b/docs/snippets/react/my-component-play-function-alt-queries.mdx.mdx
@@ -17,13 +17,4 @@ import { MyComponent } from './MyComponent';
}}>
-
- {
- // The play function interacts with the component and looks for the text
- await screen.findByText('example string');
- }}>
-
-
```
\ No newline at end of file
diff --git a/docs/snippets/react/my-component-play-function-alt-queries.ts.mdx b/docs/snippets/react/my-component-play-function-alt-queries.ts.mdx
index 8233e7f6f030..2902b937a742 100644
--- a/docs/snippets/react/my-component-play-function-alt-queries.ts.mdx
+++ b/docs/snippets/react/my-component-play-function-alt-queries.ts.mdx
@@ -24,11 +24,4 @@ export const ExampleWithRole: ComponentStoryObj = {
await userEvent.click(screen.getByRole('button', { name: / button label/i }));
},
};
-
-export const ExampleWithText: ComponentStoryObj = {
- play: async () => {
- // The play function interacts with the component and looks for the text
- await screen.findByText('example string');
- },
-};
```
diff --git a/docs/snippets/react/my-component-play-function-query-findby.js.mdx b/docs/snippets/react/my-component-play-function-query-findby.js.mdx
new file mode 100644
index 000000000000..c133b906a64f
--- /dev/null
+++ b/docs/snippets/react/my-component-play-function-query-findby.js.mdx
@@ -0,0 +1,25 @@
+```js
+// MyComponent.stories.js|jsx
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import { MyComponent } from './MyComponent';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/7.0/react/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'Async Query Methods',
+ component: MyComponent,
+};
+
+export const AsyncExample = {
+ play: async () => {
+ // Other steps
+
+ // Waits for the component to be rendered before querying the element
+ await screen.findByRole('button', { name: / button label/i }));
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/react/my-component-play-function-query-findby.mdx.mdx b/docs/snippets/react/my-component-play-function-query-findby.mdx.mdx
new file mode 100644
index 000000000000..dc3a28e2c841
--- /dev/null
+++ b/docs/snippets/react/my-component-play-function-query-findby.mdx.mdx
@@ -0,0 +1,22 @@
+```md
+
+
+import { Meta, Story } from '@storybook/addon-docs';
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import { MyComponent } from './MyComponent';
+
+
+
+ {
+ // Other steps
+
+ // Waits for the component to be rendered before querying the element
+ await screen.findByRole('button', { name: / button label/i }));
+ }}>
+
+
+```
\ No newline at end of file
diff --git a/docs/snippets/react/my-component-play-function-query-findby.ts.mdx b/docs/snippets/react/my-component-play-function-query-findby.ts.mdx
new file mode 100644
index 000000000000..6d8a48bb8e30
--- /dev/null
+++ b/docs/snippets/react/my-component-play-function-query-findby.ts.mdx
@@ -0,0 +1,29 @@
+```ts
+// MyComponent.stories.ts|tsx
+
+import React from 'react';
+
+import type { ComponentStoryObj, ComponentMeta } from '@storybook/react';
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import { MyComponent } from './MyComponent';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/7.0/react/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'Async Query Methods',
+ component: MyComponent,
+} as ComponentMeta;
+
+export const AsyncExample: ComponentStoryObj = {
+ play: async () => {
+ // Other steps
+
+ // Waits for the component to be rendered before querying the element
+ await screen.findByRole('button', { name: / button label/i }));
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/svelte/my-component-play-function-alt-queries.js.mdx b/docs/snippets/svelte/my-component-play-function-alt-queries.js.mdx
index 1385646d5086..91175c94b285 100644
--- a/docs/snippets/svelte/my-component-play-function-alt-queries.js.mdx
+++ b/docs/snippets/svelte/my-component-play-function-alt-queries.js.mdx
@@ -21,23 +21,12 @@ export default {
*/
export const ExampleWithRole = {
render: (args) => ({
- Component: Button,
+ Component: MyComponent,
props: args,
}),
play: async () => {
- // See https://storybook.js.org/docs/7.0/svelte/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
+ // See https://storybook.js.org/docs/svelte/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
await userEvent.click(screen.getByRole('button', { name: / button label/i }));
},
};
-
-export const ExampleWithText = {
- render: (args) => ({
- Component: Button,
- props: args,
- }),
- play: async () => {
- // The play function interacts with the component and looks for the text
- await screen.findByText('example string');
- },
-};
```
\ No newline at end of file
diff --git a/docs/snippets/svelte/my-component-play-function-alt-queries.mdx.mdx b/docs/snippets/svelte/my-component-play-function-alt-queries.mdx.mdx
index 01cd59c66709..c3994d606519 100644
--- a/docs/snippets/svelte/my-component-play-function-alt-queries.mdx.mdx
+++ b/docs/snippets/svelte/my-component-play-function-alt-queries.mdx.mdx
@@ -25,14 +25,4 @@ import MyComponent from './MyComponent.svelte';
Component: MyComponent,
props: args,
})} />
- {
- // The play function interacts with the component and looks for the text
- await screen.findByText('example string');
- }}
- render={(args) => ({
- Component: MyComponent,
- props: args,
- })} />
```
\ No newline at end of file
diff --git a/docs/snippets/svelte/my-component-play-function-query-findby.js.mdx b/docs/snippets/svelte/my-component-play-function-query-findby.js.mdx
new file mode 100644
index 000000000000..ddcd1e605b52
--- /dev/null
+++ b/docs/snippets/svelte/my-component-play-function-query-findby.js.mdx
@@ -0,0 +1,34 @@
+```js
+// MyComponent.stories.js
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import MyComponent from './MyComponent.svelte';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/7.0/svelte/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'Async Query Methods',
+ component: MyComponent,
+};
+
+/*
+ *π Render functions are a framework specific feature to allow you control on how the component renders.
+ * See https://storybook.js.org/docs/7.0/svelte/api/csf
+ * to learn how to use render functions.
+ */
+export const AsyncExample = {
+ render: (args) => ({
+ Component: MyComponent,
+ props: args,
+ }),
+ play: async () => {
+ // Other steps
+
+ // Waits for the component to be rendered before querying the element
+ await screen.findByRole('button', { name: / button label/i }));
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/svelte/my-component-play-function-query-findby.mdx.mdx b/docs/snippets/svelte/my-component-play-function-query-findby.mdx.mdx
new file mode 100644
index 000000000000..af2acc4a66f3
--- /dev/null
+++ b/docs/snippets/svelte/my-component-play-function-query-findby.mdx.mdx
@@ -0,0 +1,30 @@
+```md
+
+
+import { Meta, Story } from '@storybook/addon-docs';
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import MyComponent from './MyComponent.svelte';
+
+
+
+
+
+ {
+ // Other steps
+
+ // Waits for the component to be rendered before querying the element
+ await screen.findByRole('button', { name: / button label/i }));
+ }}
+ render={(args) => ({
+ Component: MyComponent,
+ props: args,
+ })} />
+```
\ No newline at end of file
diff --git a/docs/snippets/vue/my-component-play-function-alt-queries.js.mdx b/docs/snippets/vue/my-component-play-function-alt-queries.js.mdx
index e08ee1f538e0..ee1cbfc0fd60 100644
--- a/docs/snippets/vue/my-component-play-function-alt-queries.js.mdx
+++ b/docs/snippets/vue/my-component-play-function-alt-queries.js.mdx
@@ -29,15 +29,4 @@ export const ExampleWithRole = {
await userEvent.click(screen.getByRole('button', { name: / button label/i }));
},
};
-
-export const ExampleWithText = {
- render: () => ({
- components: { MyComponent },
- template: '',
- }),
- play: async () => {
- // The play function interacts with the component and looks for the text
- await screen.findByText('example string');
- },
-};
```
\ No newline at end of file
diff --git a/docs/snippets/vue/my-component-play-function-alt-queries.mdx.mdx b/docs/snippets/vue/my-component-play-function-alt-queries.mdx.mdx
index eb46c3de5a75..5db21a34f92a 100644
--- a/docs/snippets/vue/my-component-play-function-alt-queries.mdx.mdx
+++ b/docs/snippets/vue/my-component-play-function-alt-queries.mdx.mdx
@@ -19,19 +19,10 @@ import MyComponent from './MyComponent.vue';
name="ExampleWithRole"
play={ async () => {
// See https://storybook.js.org/docs/7.0/vue/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
- await userEvent.click(screen.getByRole('button'));
+ await userEvent.click(screen.getByRole('button', { name: / button label/i }));
}}
render={() => ({
components: { MyComponent },
- template: '',
- })} />
- {
- await screen.findByText('example string');
- }}
- render={() => ({
- components: { MyComponent },
- template: '',
+ template: '',
})} />
```
\ No newline at end of file
diff --git a/docs/snippets/vue/my-component-play-function-alt-queries.ts.mdx b/docs/snippets/vue/my-component-play-function-alt-queries.ts.mdx
new file mode 100644
index 000000000000..c177c34a853e
--- /dev/null
+++ b/docs/snippets/vue/my-component-play-function-alt-queries.ts.mdx
@@ -0,0 +1,36 @@
+```ts
+// MyComponent.stories.ts
+
+// import type { Meta, Story } from '@storybook/vue3'; for Vue 3
+import type { Meta, Story } from '@storybook/vue';
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import MyComponent from './MyComponent.vue';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/vue/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'QueryMethods',
+ component: MyComponent,
+} as Meta;
+
+/*
+ *π Render functions are a framework specific feature to allow you control on how the component renders.
+ * See https://storybook.js.org/docs/7.0/vue/api/csf
+ * to learn how to use render functions.
+ */
+
+export const ExampleWithRole: Story = {
+ render: () => ({
+ components: { MyComponent },
+ template: '',
+ }),
+ play: async () => {
+ // See https://storybook.js.org/docs/vue/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
+ await userEvent.click(screen.getByRole('button', { name: / button label/i }));
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/vue/my-component-play-function-composition.ts.mdx b/docs/snippets/vue/my-component-play-function-composition.ts.mdx
new file mode 100644
index 000000000000..a51e86d8905a
--- /dev/null
+++ b/docs/snippets/vue/my-component-play-function-composition.ts.mdx
@@ -0,0 +1,58 @@
+```ts
+// MyComponent.stories.ts
+
+// import type { Meta, Story } from '@storybook/vue3'; for Vue 3
+import type { Meta, Story } from '@storybook/vue';
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import MyComponent from './MyComponent.vue';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/7.0/vue/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'MyComponent',
+ component: MyComponent,
+} as Meta;
+
+/*
+ *π Render functions are a framework specific feature to allow you control on how the component renders.
+ * See https://storybook.js.org/docs/7.0/vue/api/csf
+ * to learn how to use render functions.
+ */
+
+export const FirstStory: Story = {
+ render: () => ({
+ components: { MyComponent },
+ template: '',
+ }),
+ play: async () => {
+ userEvent.type(screen.getByTestId('an-element'), 'example-value');
+ },
+};
+
+export const SecondStory: Story = {
+ render: () => ({
+ components: { MyComponent },
+ template: '',
+ }),
+ play: async () => {
+ await userEvent.type(screen.getByTestId('other-element'), 'another value');
+ },
+};
+
+export const CombinedStories: Story = {
+ render: () => ({
+ components: { MyComponent },
+ template: '',
+ }),
+ play: async () => {
+ // Runs the FirstStory and Second story play function before running this story's play function
+ await FirstStory.play();
+ await SecondStory.play();
+ await userEvent.type(screen.getByTestId('another-element'), 'random value');
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/vue/my-component-play-function-query-findby.js.mdx b/docs/snippets/vue/my-component-play-function-query-findby.js.mdx
new file mode 100644
index 000000000000..3c2097674dc1
--- /dev/null
+++ b/docs/snippets/vue/my-component-play-function-query-findby.js.mdx
@@ -0,0 +1,34 @@
+```js
+// MyComponent.stories.js
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import MyComponent from './MyComponent.vue';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/7.0/vue/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'Async Query Methods',
+ component: MyComponent,
+};
+
+/*
+ *π Render functions are a framework specific feature to allow you control on how the component renders.
+ * See https://storybook.js.org/docs/7.0/vue/api/csf
+ * to learn how to use render functions.
+ */
+export const AsyncExample = {
+ render: () => ({
+ components: { MyComponent },
+ template: '',
+ }),
+ play: async () => {
+ // Other steps
+
+ // Waits for the component to be rendered before querying the element
+ await screen.findByRole('button', { name: / button label/i }));
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/vue/my-component-play-function-query-findby.mdx.mdx b/docs/snippets/vue/my-component-play-function-query-findby.mdx.mdx
new file mode 100644
index 000000000000..0109bc0d69b6
--- /dev/null
+++ b/docs/snippets/vue/my-component-play-function-query-findby.mdx.mdx
@@ -0,0 +1,30 @@
+```md
+
+
+import { Meta, Story } from '@storybook/addon-docs';
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import MyComponent from './MyComponent.vue';
+
+
+
+
+
+ {
+ // Other steps
+
+ // Waits for the component to be rendered before querying the element
+ await screen.findByRole('button', { name: / button label/i }));
+ }}
+ render={() => ({
+ components: { MyComponent },
+ template: '',
+ })} />
+```
\ No newline at end of file
diff --git a/docs/snippets/vue/my-component-play-function-query-findby.ts.mdx b/docs/snippets/vue/my-component-play-function-query-findby.ts.mdx
new file mode 100644
index 000000000000..4b880a8ed4fb
--- /dev/null
+++ b/docs/snippets/vue/my-component-play-function-query-findby.ts.mdx
@@ -0,0 +1,38 @@
+```js
+// MyComponent.stories.ts
+
+// import type { Meta, Story } from '@storybook/vue3'; for Vue 3
+import type { Meta, Story } from '@storybook/vue';
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import MyComponent from './MyComponent.vue';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/vue/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'Async Query Methods',
+ component: MyComponent,
+} as Meta;
+
+/*
+ *π Render functions are a framework specific feature to allow you control on how the component renders.
+ * See https://storybook.js.org/docs/7.0/vue/api/csf
+ * to learn how to use render functions.
+ */
+
+export const AsyncExample: Story = {
+ render: () => ({
+ components: { MyComponent },
+ template: '',
+ }),
+ play: async () => {
+ // Other steps
+
+ // Waits for the component to be rendered before querying the element
+ await screen.findByRole('button', { name: / button label/i }));
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/vue/my-component-play-function-waitfor.js.mdx b/docs/snippets/vue/my-component-play-function-waitfor.js.mdx
index afe123c71c7e..824a51ce6c38 100644
--- a/docs/snippets/vue/my-component-play-function-waitfor.js.mdx
+++ b/docs/snippets/vue/my-component-play-function-waitfor.js.mdx
@@ -25,7 +25,7 @@ export const ExampleAsyncStory = {
template: '',
}),
play: async () => {
- const exampleElement = screen.getByLabelText('example-element', {
+ const exampleElement = screen.getByLabelText('Username', {
selector: 'input',
});
diff --git a/docs/snippets/vue/my-component-play-function-waitfor.ts.mdx b/docs/snippets/vue/my-component-play-function-waitfor.ts.mdx
new file mode 100644
index 000000000000..8fc40d53df67
--- /dev/null
+++ b/docs/snippets/vue/my-component-play-function-waitfor.ts.mdx
@@ -0,0 +1,47 @@
+```ts
+// MyComponent.stories.ts
+
+// import type { Meta, Story } from '@storybook/vue3'; for Vue 3
+import type { Meta, Story } from '@storybook/vue';
+
+import { screen, userEvent, waitFor } from '@storybook/testing-library';
+
+import MyComponent from './MyComponent.vue';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/vue/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'WithAsync',
+ component: MyComponent,
+} as Meta;
+
+/*
+ *π Render functions are a framework specific feature to allow you control on how the component renders.
+ * See https://storybook.js.org/docs/7.0/vue/api/csf
+ * to learn how to use render functions.
+ */
+
+export const ExampleAsyncStory: Story = {
+ render: () => ({
+ components: { MyComponent },
+ template: '',
+ }),
+ play: async () => {
+ const Input = screen.getByLabelText('Username', {
+ selector: 'input',
+ });
+ await userEvent.type(Input, 'WrongInput', {
+ delay: 100,
+ });
+ // See https://storybook.js.org/docs/7.0/vue/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
+ const Submit = screen.getByRole('button');
+ await userEvent.click(Submit);
+
+ await waitFor(async () => {
+ await userEvent.hover(screen.getByTestId('error'));
+ });
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/vue/my-component-play-function-with-canvas.js.mdx b/docs/snippets/vue/my-component-play-function-with-canvas.js.mdx
index c1ecae5aabfa..5a972cb4d332 100644
--- a/docs/snippets/vue/my-component-play-function-with-canvas.js.mdx
+++ b/docs/snippets/vue/my-component-play-function-with-canvas.js.mdx
@@ -30,7 +30,7 @@ export const ExampleStory = {
// Starts querying from the component's root element
await userEvent.type(canvas.getByTestId('example-element'), 'something');
- await userEvent.type(canvas.getByTestId('another-element'), 'something else');
+ await userEvent.click(canvas.getByRole('another-element'));
},
};
```
\ No newline at end of file
diff --git a/docs/snippets/vue/my-component-play-function-with-canvas.ts.mdx b/docs/snippets/vue/my-component-play-function-with-canvas.ts.mdx
new file mode 100644
index 000000000000..88882b52ee0c
--- /dev/null
+++ b/docs/snippets/vue/my-component-play-function-with-canvas.ts.mdx
@@ -0,0 +1,40 @@
+```ts
+
+// MyComponent.stories.ts
+
+// import type { Meta, Story } from '@storybook/vue3'; for Vue 3
+import type { Meta, Story } from '@storybook/vue';
+
+import { getByRole, userEvent, within } from '@storybook/testing-library';
+
+import MyComponent from './MyComponent.vue';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/7.0/vue/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'WithCanvasElement',
+ component: MyComponent,
+} as Meta;
+
+/*
+ *π Render functions are a framework specific feature to allow you control on how the component renders.
+ * See https://storybook.js.org/docs/7.0/vue/api/csf
+ * to learn how to use render functions.
+ */
+export const ExampleStory: Story = {
+ render: () => ({
+ components: { MyComponent },
+ template: '',
+ }),
+ play: async ({ canvasElement }) => {
+ // Assigns canvas to the component root element
+ const canvas = within(canvasElement);
+
+ // Starts querying from the component's root element
+ await userEvent.type(canvas.getByTestId('example-element'), 'something');
+ await userEvent.click(canvas.getByRole('another-element'));
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/vue/my-component-play-function-with-clickevent.ts.mdx b/docs/snippets/vue/my-component-play-function-with-clickevent.ts.mdx
new file mode 100644
index 000000000000..0c11379e6fec
--- /dev/null
+++ b/docs/snippets/vue/my-component-play-function-with-clickevent.ts.mdx
@@ -0,0 +1,47 @@
+```ts
+// MyComponent.stories.ts
+
+// import type { Meta, Story } from '@storybook/vue3'; for Vue 3
+import type { Meta, Story } from '@storybook/vue';
+
+import { fireEvent, screen, userEvent } from '@storybook/testing-library';
+
+import MyComponent from './MyComponent.vue';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/7.0/vue/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'ClickExamples',
+ component: MyComponent,
+} as Meta;
+
+/*
+ *π Render functions are a framework specific feature to allow you control on how the component renders.
+ * See https://storybook.js.org/docs/7.0/vue/api/csf
+ * to learn how to use render functions.
+ */
+
+export const ClickExample: Story = {
+ render: () => ({
+ components: { MyComponent },
+ template: '',
+ }),
+ play: async () => {
+ // See https://storybook.js.org/docs/7.0/vue/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
+ await userEvent.click(screen.getByRole('button'));
+ },
+};
+
+export const FireEventExample: Story = {
+ render: () => ({
+ components: { MyComponent },
+ template: '',
+ }),
+ play: async () => {
+ // See https://storybook.js.org/docs/7.0/vue/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
+ await fireEvent.click(screen.getByTestId('data-testid'));
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/vue/my-component-play-function-with-delay.ts.mdx b/docs/snippets/vue/my-component-play-function-with-delay.ts.mdx
new file mode 100644
index 000000000000..3c70bff040dd
--- /dev/null
+++ b/docs/snippets/vue/my-component-play-function-with-delay.ts.mdx
@@ -0,0 +1,44 @@
+```ts
+// MyComponent.stories.ts
+
+// import type { Meta, Story } from '@storybook/vue3'; for Vue 3
+import type { Meta, Story } from '@storybook/vue';
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import MyComponent from './MyComponent.vue';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/7.0/vue/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'WithDelay',
+ component: MyComponent,
+} as Meta;
+
+/*
+ *π Render functions are a framework specific feature to allow you control on how the component renders.
+ * See https://storybook.js.org/docs/7.0/vue/api/csf
+ * to learn how to use render functions.
+ */
+export const DelayedStory: Story = {
+ render: () => ({
+ components: { MyComponent },
+ template: '',
+ }),
+ play: async () => {
+ const exampleElement = screen.getByLabelText('example-element');
+
+ // The delay option set the ammount of milliseconds between characters being typed
+ await userEvent.type(exampleElement, 'random string', {
+ delay: 100,
+ });
+
+ const AnotherExampleElement = screen.getByLabelText('another-example-element');
+ await userEvent.type(AnotherExampleElement, 'another random string', {
+ delay: 100,
+ });
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/vue/my-component-play-function-with-selectevent.ts.mdx b/docs/snippets/vue/my-component-play-function-with-selectevent.ts.mdx
new file mode 100644
index 000000000000..88728586349f
--- /dev/null
+++ b/docs/snippets/vue/my-component-play-function-with-selectevent.ts.mdx
@@ -0,0 +1,48 @@
+```ts
+// MyComponent.stories.ts
+
+// import type { Meta, Story } from '@storybook/vue3'; for Vue 3
+import type { Meta, Story } from '@storybook/vue';
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import MyComponent from './MyComponent.vue';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/7.0/vue/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'WithSelectEvent',
+ component: WithSelectEvent,
+} as Meta;
+
+// Custom function to emulate a pause
+function sleep(ms: number) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+/*
+ *π Render functions are a framework specific feature to allow you control on how the component renders.
+ * See https://storybook.js.org/docs/7.0/vue/api/csf
+ * to learn how to use render functions.
+ */
+
+export const ExampleChangeEvent: Story = {
+ render: () => ({
+ components: { MyComponent },
+ template: '',
+ }),
+ play: async () => {
+ const select = screen.getByRole('listbox');
+
+ await userEvent.selectOptions(select, ['One Item']);
+ await sleep(2000);
+
+ await userEvent.selectOptions(select, ['Another Item']);
+ await sleep(2000);
+
+ await userEvent.selectOptions(select, ['Yet another item']);
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/vue/register-component-with-play-function.mdx.mdx b/docs/snippets/vue/register-component-with-play-function.mdx.mdx
index d133cc0e8fbd..a77bf6f70344 100644
--- a/docs/snippets/vue/register-component-with-play-function.mdx.mdx
+++ b/docs/snippets/vue/register-component-with-play-function.mdx.mdx
@@ -35,8 +35,9 @@ import RegistrationForm from './RegistrationForm.vue';
});
// See https://storybook.js.org/docs/7.0/vue/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
- const Submit = screen.getByRole('button');
- await userEvent.click(Submit);
+ const submitButton = screen.getByRole('button');
+
+ await userEvent.click(submitButton);
}}
render={() => ({
components: { RegistrationForm },
diff --git a/docs/snippets/vue/register-component-with-play-function.ts.mdx b/docs/snippets/vue/register-component-with-play-function.ts.mdx
new file mode 100644
index 000000000000..4bb6bd2c4416
--- /dev/null
+++ b/docs/snippets/vue/register-component-with-play-function.ts.mdx
@@ -0,0 +1,52 @@
+```ts
+// RegistrationForm.stories.ts
+
+// import type { Meta, Story } from '@storybook/vue3'; for Vue 3
+import type { Meta, Story } from '@storybook/vue';
+
+import { screen, userEvent } from '@storybook/testing-library';
+
+import RegistrationForm from './RegistrationForm.vue';
+
+export default {
+ /* π The title prop is optional.
+ * See https://storybook.js.org/docs/7.0/vue/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'RegistrationForm',
+ component: RegistrationForm,
+} as Meta;
+
+/*
+ *π Render functions are a framework specific feature to allow you control on how the component renders.
+ * See https://storybook.js.org/docs/7.0/vue/api/csf
+ * to learn how to use render functions.
+ */
+export const FilledForm: Story = {
+ render: () => ({
+ components: { RegistrationForm },
+ template: '',
+ }),
+ play: async () => {
+ const emailInput = screen.getByLabelText('email', {
+ selector: 'input',
+ });
+
+ await userEvent.type(emailInput, 'example-email@email.com', {
+ delay: 100,
+ });
+
+ const passwordInput = screen.getByLabelText('password', {
+ selector: 'input',
+ });
+
+ await userEvent.type(passwordInput, 'ExamplePassword', {
+ delay: 100,
+ });
+
+ // See https://storybook.js.org/docs/7.0/vue/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
+ const submitButton = screen.getByRole('button');
+ await userEvent.click(submitButton);
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/why-storybook.md b/docs/why-storybook.md
index 1f92e7c381cf..b640ff11699f 100644
--- a/docs/why-storybook.md
+++ b/docs/why-storybook.md
@@ -16,7 +16,7 @@ The breadth of modern frontends overwhelm existing workflows. Developers must co
## The solution
-**Build UIs in isolation**
+#### Build UIs in isolation
Every piece of UI is now a [component](https://www.componentdriven.org/). The superpower of components is that you don't need to spin up the whole app just to see how they render. You can render a specific variation in isolation by passing in props, mocking data, or faking events.
@@ -29,7 +29,7 @@ Storybook is packaged as a small, development-only, [workshop](https://bradfrost
/>
-**Capture UI variations as βstoriesβ**
+#### Capture UI variations as βstoriesβ
When developing a component variation in isolation, save it as a story. [Stories](https://github.com/ComponentDriven/csf) are a declarative syntax for supplying props and mock data to simulate component variations. Each component can have multiple stories. Each story allows you to demonstrate a specific variation of that component to verify appearance and behavior.
@@ -62,7 +62,7 @@ You write stories for granular UI component variation and then use those stories
-**Storybook keeps track of every story**
+#### Storybook keeps track of every story
Storybook is an interactive directory of your UI components and their stories. In the past, you'd have to spin up the app, navigate to a page, and contort the UI into the right state. This is a huge waste of time and bogs down frontend development. With Storybook, you can skip all those steps and jump straight to working on a UI component in a specific state.
@@ -109,23 +109,23 @@ Most community members choose a [Component-Driven](https://www.componentdriven.o
When you write stories for components, you get a bunch of additional benefits for free.
-**π Develop UIs that are more durable**
+#### π Develop UIs that are more durable
Isolate components and pages and track their use cases as [stories](./writing-stories/introduction.md). Verify hard-to-reach edge cases of UI. Use addons to mock everything a component needsβcontext, API requests, device features, etc.
-**β Test UIs with less effort and no flakes**
+#### β Test UIs with less effort and no flakes
Stories are a pragmatic, reproducible way of tracking UI states. Use them to spot-test the UI during development. Storybook offers built-in workflows for automated [Accessibility](./writing-tests/accessibility-testing.md), [Interaction](./writing-tests/interaction-testing.md), and [Visual](./writing-tests/visual-testing.md) testing. Or use stories as test cases by importing them into other JavaScript testing tools.
-**π Document UI for your team to reuse**
+#### π Document UI for your team to reuse
Storybook is the single source of truth for your UI. Stories index all your components and their various states, making it easy for your team to find and reuse existing UI patterns. Storybook also auto-generates [documentation](./writing-docs/introduction.md) from those stories.
-**π€ Share how the UI actually works**
+#### π€ Share how the UI actually works
Stories show how UIs actually work, not just a picture of how they're supposed to work. That keeps everyone aligned on what's currently in production. [Publish Storybook](./sharing/publish-storybook.md) to get sign-off from teammates. Or [embed](./sharing/embed.md) them in wikis, Markdown, and Figma to streamline collaboration.
-**π¦Automate UI workflows**
+#### π¦Automate UI workflows
Storybook is compatible with your continuous integration workflow. Add it as a CI step to automate user interface testing, review implementation with teammates, and get signoff from stakeholders.
diff --git a/docs/writing-stories/play-function.md b/docs/writing-stories/play-function.md
index b95c7514ff0f..05a2a6ed768c 100644
--- a/docs/writing-stories/play-function.md
+++ b/docs/writing-stories/play-function.md
@@ -47,6 +47,7 @@ Storybook's `play` functions are small code snippets that run once the story fin
'angular/register-component-with-play-function.ts.mdx',
'angular/register-component-with-play-function.mdx.mdx',
'vue/register-component-with-play-function.js.mdx',
+ 'vue/register-component-with-play-function.ts.mdx',
'vue/register-component-with-play-function.mdx.mdx',
'svelte/register-component-with-play-function.js.mdx',
'svelte/register-component-with-play-function.mdx.mdx',
@@ -75,6 +76,7 @@ Thanks to the [Component Story Format](../api/csf.md), an ES6 module based file
'react/my-component-play-function-composition.ts.mdx',
'angular/my-component-play-function-composition.ts.mdx',
'vue/my-component-play-function-composition.js.mdx',
+ 'vue/my-component-play-function-composition.ts.mdx',
'svelte/my-component-play-function-composition.js.mdx',
]}
/>
@@ -99,6 +101,7 @@ A common type of component interaction is a button click. If you need to reprodu
'angular/my-component-play-function-with-clickevent.ts.mdx',
'angular/my-component-play-function-with-clickevent.mdx.mdx',
'vue/my-component-play-function-with-clickevent.js.mdx',
+ 'vue/my-component-play-function-with-clickevent.ts.mdx',
'vue/my-component-play-function-with-clickevent.mdx.mdx',
'svelte/my-component-play-function-with-clickevent.js.mdx',
'svelte/my-component-play-function-with-clickevent.mdx.mdx',
@@ -121,6 +124,7 @@ Asides from click events, you can also script additional events with the `play`
'angular/my-component-play-function-with-selectevent.ts.mdx',
'angular/my-component-play-function-with-selectevent.mdx.mdx',
'vue/my-component-play-function-with-selectevent.js.mdx',
+ 'vue/my-component-play-function-with-selectevent.ts.mdx',
'vue/my-component-play-function-with-selectevent.mdx.mdx',
'svelte/my-component-play-function-with-selectevent.js.mdx',
'svelte/my-component-play-function-with-selectevent.mdx.mdx',
@@ -141,6 +145,7 @@ In addition to events, you can also create interactions with the `play` function
'angular/my-component-play-function-with-delay.ts.mdx',
'angular/my-component-play-function-with-delay.mdx.mdx',
'vue/my-component-play-function-with-delay.js.mdx',
+ 'vue/my-component-play-function-with-delay.ts.mdx',
'vue/my-component-play-function-with-delay.mdx.mdx',
'svelte/my-component-play-function-with-delay.js.mdx',
'svelte/my-component-play-function-with-delay.mdx.mdx',
@@ -163,6 +168,7 @@ You can also use the `play` function to verify the existence of an element based
'angular/my-component-play-function-waitfor.ts.mdx',
'angular/my-component-play-function-waitfor.mdx.mdx',
'vue/my-component-play-function-waitfor.js.mdx',
+ 'vue/my-component-play-function-waitfor.ts.mdx',
'vue/my-component-play-function-waitfor.mdx.mdx',
'svelte/my-component-play-function-waitfor.js.mdx',
'svelte/my-component-play-function-waitfor.mdx.mdx',
@@ -185,6 +191,7 @@ If you need, you can also adjust your `play` function to find elements based on
'angular/my-component-play-function-alt-queries.ts.mdx',
'angular/my-component-play-function-alt-queries.mdx.mdx',
'vue/my-component-play-function-alt-queries.js.mdx',
+ 'vue/my-component-play-function-alt-queries.ts.mdx',
'vue/my-component-play-function-alt-queries.mdx.mdx',
'svelte/my-component-play-function-alt-queries.js.mdx',
'svelte/my-component-play-function-alt-queries.mdx.mdx',
@@ -197,6 +204,29 @@ If you need, you can also adjust your `play` function to find elements based on
π‘ You can read more about the querying elements in the Testing library documentation.
+When Storybook loads the story, the `play` function starts its execution and queries the DOM tree expecting the element to be available when the story renders. In case there's a failure in your test, you'll be able to verify its root cause quickly.
+
+Otherwise, if the component is not immediately available, for instance, due to a previous step defined inside your `play` function or some asynchronous behavior, you can adjust your story and wait for the change to the DOM tree to happen before querying the element. For example:
+
+
+
+
+
+
+
## Working with the Canvas
By default, each interaction you write inside your `play` function will be executed starting from the top-level element of the Canvas. This is acceptable for smaller components (e.g., buttons, checkboxes, text inputs), but can be inefficient for complex components (e.g., forms, pages), or for multiple stories. To accommodate this, you can adjust your interactions to start execution from the component's root. For example:
@@ -211,6 +241,7 @@ By default, each interaction you write inside your `play` function will be execu
'angular/my-component-play-function-with-canvas.ts.mdx',
'angular/my-component-play-function-with-canvas.mdx.mdx',
'vue/my-component-play-function-with-canvas.js.mdx',
+ 'vue/my-component-play-function-with-canvas.ts.mdx',
'vue/my-component-play-function-with-canvas.mdx.mdx',
'svelte/my-component-play-function-with-canvas.js.mdx',
'svelte/my-component-play-function-with-canvas.mdx.mdx',
diff --git a/scripts/check-package.js b/scripts/check-package.js
index 42fac6081ed7..dc675d9bff1a 100644
--- a/scripts/check-package.js
+++ b/scripts/check-package.js
@@ -97,8 +97,8 @@ async function run() {
selection?.filter(Boolean).forEach(async (v) => {
const commmand = (await readJSON(resolve(v.location, 'package.json'))).scripts.check;
- const cwd = resolve(__dirname, '..', v.location);
- const sub = require('execa').command(`yarn ${commmand}${watchMode ? ' --watch' : ''}`, {
+ const cwd = resolve(__dirname, '..', 'code', v.location);
+ const sub = require('execa').command(`${commmand}${watchMode ? ' --watch' : ''}`, {
cwd,
buffer: false,
shell: true,
diff --git a/scripts/sandbox.ts b/scripts/sandbox.ts
index 36dc21bad97f..07e85b15cd5c 100644
--- a/scripts/sandbox.ts
+++ b/scripts/sandbox.ts
@@ -14,7 +14,6 @@ import prompts from 'prompts';
import type { AbortController } from 'node-abort-controller';
import command from 'execa';
-import dedent from 'ts-dedent';
import { createOptions, getOptionsOrPrompt, OptionValues } from './utils/options';
import { executeCLIStep } from './utils/cli-step';
import { installYarn2, configureYarn2ForVerdaccio, addPackageResolutions } from './utils/yarn';
@@ -23,6 +22,8 @@ import { getInterpretedFile } from '../code/lib/core-common';
import { ConfigFile, readConfig, writeConfig } from '../code/lib/csf-tools';
import { babelParse } from '../code/lib/csf-tools/src/babelParse';
import TEMPLATES from '../code/lib/cli/src/repro-templates';
+import { detectLanguage } from '../code/lib/cli/src/detect';
+import { SupportedLanguage } from '../code/lib/cli/src/project_types';
import { servePackages } from './utils/serve-packages';
import { filterExistsInCodeDir, codeDir } from './utils/filterExistsInCodeDir';
import { JsPackageManagerFactory } from '../code/lib/cli/src/js-package-manager';
@@ -267,43 +268,51 @@ function addPreviewAnnotations(mainConfig: ConfigFile, paths: string[]) {
mainConfig.setFieldValue(['previewAnnotations'], [...(config || []), ...paths]);
}
-// paths are of the form 'renderers/react', 'addons/actions'
-async function addStories(
- packageDirs: string[],
- { mainConfig, cwd }: { mainConfig: ConfigFile; cwd: string }
+// packageDir is eg 'renderers/react', 'addons/actions'
+async function linkPackageStories(
+ packageDir: string,
+ { mainConfig, cwd, linkInDir }: { mainConfig: ConfigFile; cwd: string; linkInDir?: string }
) {
- // Link `stories` directories
- // '../../../code/lib/store/template/stories' to 'src/templates/lib/store'
+ const source = path.join(codeDir, packageDir, 'template', 'stories');
+ // By default we link `stories` directories
+ // e.g '../../../code/lib/store/template/stories' to 'template-stories/lib/store'
// if the directory /lib/store/template/stories exists
//
- // We link rather than reference relative dir to avoid Running two versions
- // of React in react-based projects
- await Promise.all(
- packageDirs.map(async (p) => {
- const source = path.join(codeDir, p, 'template', 'stories');
- await ensureSymlink(source, path.resolve(cwd, 'template-stories', p));
- })
- );
+ // The files must be linked in the cwd, in order to ensure that any dependencies they
+ // reference are resolved in the cwd. In particular 'react' resolved by MDX files.
+ const target = linkInDir
+ ? path.resolve(linkInDir, packageDir)
+ : path.resolve(cwd, 'template-stories', packageDir);
+ await ensureSymlink(source, target);
+
+ // Add `previewAnnotation` entries of the form
+ // './template-stories/lib/store/preview.ts'
+ // if the file /lib/store/template/stories/preview.ts exists
+ const previewFile = path.join(codeDir, packageDir, 'template', 'stories', 'preview.ts');
+ if (await pathExists(previewFile)) {
+ addPreviewAnnotations(mainConfig, [
+ `./${path.join('template-stories', packageDir, 'preview.ts')}`,
+ ]);
+ }
+}
+
+// Update the stories field to ensure that:
+// a) no TS files that are linked from the renderer are picked up in non-TS projects
+// b) files in ./template-stories are not matched by the default glob
+async function updateStoriesField(mainConfig: ConfigFile, isJs: boolean) {
const stories = mainConfig.getFieldValue(['stories']) as string[];
+
+ // If the project is a JS project, let's make sure any linked in TS stories from the
+ // renderer inside src|stories are simply ignored.
+ const updatedStories = isJs
+ ? stories.map((specifier) => specifier.replace('js|jsx|ts|tsx', 'js|jsx'))
+ : stories;
+
// FIXME: '*.@(mdx|stories.mdx|stories.tsx|stories.ts|stories.jsx|stories.js'
const linkedStories = path.join('..', 'template-stories', '**', '*.stories.@(js|jsx|ts|tsx|mdx)');
- mainConfig.setFieldValue(['stories'], [...stories, linkedStories]);
- // Add `config` entries of the form
- // '../../code/lib/store/template/stories/preview.ts'
- // if the file /lib/store/template/stories/preview.ts exists
- const packageDirsWithPreview = await filterExistsInCodeDir(
- packageDirs,
- path.join('template', 'stories', 'preview.ts')
- );
-
- const config = mainConfig.getFieldValue(['config']) as string[];
- const extraConfig = packageDirsWithPreview.map((p) => {
- const previewFile = path.join('template-stories', p, 'preview.ts');
- return `./${previewFile}`;
- });
- mainConfig.setFieldValue(['config'], [...(config || []), ...extraConfig]);
+ mainConfig.setFieldValue(['stories'], [...updatedStories, linkedStories]);
}
type Workspace = { name: string; location: string };
@@ -408,10 +417,19 @@ export async function sandbox(optionValues: OptionValues) {
);
addPreviewAnnotations(mainConfig, [`.${path.sep}${path.join(storiesPath, 'components')}`]);
- // Link in the stories from the store, the renderer and the addons
- const storiesToAdd = [] as string[];
- storiesToAdd.push(workspacePath('core package', '@storybook/store', workspaces));
- storiesToAdd.push(rendererPath);
+ // Add stories for the renderer. NOTE: these *do* need to be processed by the framework build system
+ await linkPackageStories(rendererPath, {
+ mainConfig,
+ cwd,
+ linkInDir: path.resolve(cwd, storiesPath),
+ });
+
+ // Add stories for lib/store (and addons below). NOTE: these stories will be in the
+ // template-stories folder and *not* processed by the framework build config (instead by esbuild-loader)
+ await linkPackageStories(workspacePath('core package', '@storybook/store', workspaces), {
+ mainConfig,
+ cwd,
+ });
// TODO -- sb add doesn't actually work properly:
// - installs in `deps` not `devDeps`
@@ -423,17 +441,23 @@ export async function sandbox(optionValues: OptionValues) {
await executeCLIStep(steps.add, { argument: addonName, cwd, dryRun, debug });
}
- for (const addon of [...defaultAddons, ...optionValues.addon]) {
- storiesToAdd.push(workspacePath('addon', `@storybook/addon-${addon}`, workspaces));
- }
+ const addonDirs = [...defaultAddons, ...optionValues.addon].map((addon) =>
+ workspacePath('addon', `@storybook/addon-${addon}`, workspaces)
+ );
const existingStories = await filterExistsInCodeDir(
- storiesToAdd,
+ addonDirs,
path.join('template', 'stories')
);
- await addStories(existingStories, {
+ await Promise.all(
+ existingStories.map(async (packageDir) => linkPackageStories(packageDir, { mainConfig, cwd }))
+ );
+
+ // Ensure that we match stories from the template-stories dir
+ const packageJson = await import(path.join(cwd, 'package.json'));
+ await updateStoriesField(
mainConfig,
- cwd,
- });
+ detectLanguage(packageJson) === SupportedLanguage.JAVASCRIPT
+ );
// Add some extra settings (see above for what these do)
mainConfig.setFieldValue(['core', 'disableTelemetry'], true);
diff --git a/scripts/tasks/chromatic.ts b/scripts/tasks/chromatic.ts
index 8e33d9baa23a..f30892fc28f9 100644
--- a/scripts/tasks/chromatic.ts
+++ b/scripts/tasks/chromatic.ts
@@ -18,7 +18,8 @@ export const chromatic: Task = {
--storybook-build-dir=${builtSandboxDir} \
--junit-report=${junitFilename} \
--projectToken=${token}`,
- { cwd: sandboxDir }
+ { cwd: sandboxDir },
+ { debug: true }
);
} finally {
if (fs.existsSync(junitFilename)) {