From 409d85e0c32af775d7a3a643059405c5d5cbd96f Mon Sep 17 00:00:00 2001 From: Moonliujk Date: Sat, 20 Jul 2024 12:30:26 +0800 Subject: [PATCH 1/2] update the way of creating context in "PureInboxScreen.stories.js" because there is no 'app' export from the @storybook/vue3 module --- content/intro-to-storybook/vue/zh-CN/screen.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/intro-to-storybook/vue/zh-CN/screen.md b/content/intro-to-storybook/vue/zh-CN/screen.md index f97273721..df8ec04fa 100644 --- a/content/intro-to-storybook/vue/zh-CN/screen.md +++ b/content/intro-to-storybook/vue/zh-CN/screen.md @@ -165,11 +165,11 @@ Error.args = { error: true }; 好消息是在 story 中的 `PureInboxScreen` 中使用 Pinia store 十分容易!我们可以更新 story 并直接导入在上一章中创建的 Pinia store。 ```diff:title=src/components/PureInboxScreen.stories.js -+ import { app } from '@storybook/vue3'; ++ import { setup } from '@storybook/vue3'; + import { createPinia } from 'pinia'; -+ app.use(createPinia()); ++ setup((app) => {app.use(createPinia())}); import PureInboxScreen from './PureInboxScreen.vue'; From 570f754b2a3b156620692f002c121db61d5d7b86 Mon Sep 17 00:00:00 2001 From: Moonliujk Date: Tue, 23 Jul 2024 21:23:07 +0800 Subject: [PATCH 2/2] doc: update doc(zh-CN) --- .../vue/zh-CN/composite-component.md | 82 +++++---- .../vue/zh-CN/conclusion.md | 2 +- content/intro-to-storybook/vue/zh-CN/data.md | 70 ++++---- .../intro-to-storybook/vue/zh-CN/deploy.md | 22 ++- .../vue/zh-CN/get-started.md | 9 +- .../intro-to-storybook/vue/zh-CN/screen.md | 156 +++++++----------- .../vue/zh-CN/simple-component.md | 139 ++++++++-------- content/intro-to-storybook/vue/zh-CN/test.md | 2 +- .../vue/zh-CN/using-addons.md | 18 +- 9 files changed, 236 insertions(+), 264 deletions(-) diff --git a/content/intro-to-storybook/vue/zh-CN/composite-component.md b/content/intro-to-storybook/vue/zh-CN/composite-component.md index 3f9f504fd..cda8d421a 100644 --- a/content/intro-to-storybook/vue/zh-CN/composite-component.md +++ b/content/intro-to-storybook/vue/zh-CN/composite-component.md @@ -89,62 +89,60 @@ import * as TaskStories from './Task.stories'; export default { component: TaskList, title: 'TaskList', + tags: ['autodocs'], decorators: [() => ({ template: '
' })], argTypes: { - onPinTask: {}, - onArchiveTask: {}, + ...TaskStories.ActionsData, }, }; -const Template = args => ({ - components: { TaskList }, - setup() { - return { args, ...TaskStories.actionsData }; +export const Default = { + args: { + // Shaping the stories through args composition. + // The data was inherited from the Default story in Task.stories.js. + tasks: [ + { ...TaskStories.Default.args.task, id: '1', title: 'Task 1' }, + { ...TaskStories.Default.args.task, id: '2', title: 'Task 2' }, + { ...TaskStories.Default.args.task, id: '3', title: 'Task 3' }, + { ...TaskStories.Default.args.task, id: '4', title: 'Task 4' }, + { ...TaskStories.Default.args.task, id: '5', title: 'Task 5' }, + { ...TaskStories.Default.args.task, id: '6', title: 'Task 6' }, + ], }, - template: '', -}); - -export const Default = Template.bind({}); -Default.args = { - // Shaping the stories through args composition. - // The data was inherited from the Default story in task.stories.js. - tasks: [ - { ...TaskStories.Default.args.task, id: '1', title: 'Task 1' }, - { ...TaskStories.Default.args.task, id: '2', title: 'Task 2' }, - { ...TaskStories.Default.args.task, id: '3', title: 'Task 3' }, - { ...TaskStories.Default.args.task, id: '4', title: 'Task 4' }, - { ...TaskStories.Default.args.task, id: '5', title: 'Task 5' }, - { ...TaskStories.Default.args.task, id: '6', title: 'Task 6' }, - ], }; -export const WithPinnedTasks = Template.bind({}); -WithPinnedTasks.args = { - // Shaping the stories through args composition. - // Inherited data coming from the Default story. - tasks: [ - ...Default.args.tasks.slice(0, 5), - { id: '6', title: 'Task 6 (pinned)', state: 'TASK_PINNED' }, - ], +export const WithPinnedTasks = { + args: { + // Shaping the stories through args composition. + // Inherited data coming from the Default story. + tasks: [ + ...Default.args.tasks.slice(0, 5), + { id: '6', title: 'Task 6 (pinned)', state: 'TASK_PINNED' }, + ], + }, }; -export const Loading = Template.bind({}); -Loading.args = { - tasks: [], - loading: true, +export const Loading = { + args: { + tasks: [], + loading: true, + }, }; -export const Empty = Template.bind({}); -Empty.args = { - // Shaping the stories through args composition. - // Inherited data coming from the Loading story. - ...Loading.args, - loading: false, +export const Empty = { + args: { + // Shaping the stories through args composition. + // Inherited data coming from the Loading story. + ...Loading.args, + loading: false, + }, }; ```
-Decorators - 装饰器 提供了一种任意包装 story 的方法。上述例子中我们使用默认导出的 decorator 关键字给渲染的组件周围添加一些 margin。但是装饰器也可以给组件添加其他上下文,详见下文。 + +[**Decorators - 装饰器**](https://storybook.js.org/docs/writing-stories/decorators) 提供了一种任意包装 story 的方法。上述例子中我们使用默认导出的 decorator 关键字给渲染的组件周围添加一些 `margin`。但是装饰器也可以给组件添加其他上下文,详见下文。 +
通过导入 `TaskStories`,我们能够以最小的代价[组合](https://storybook.js.org/docs/vue/writing-stories/args#args-composition) story 中的参数(argument)。这样,就为每个组件保留了其所需的数据和 action(模拟回调)。 @@ -153,7 +151,7 @@ Empty.args = { @@ -239,7 +237,7 @@ export default { diff --git a/content/intro-to-storybook/vue/zh-CN/conclusion.md b/content/intro-to-storybook/vue/zh-CN/conclusion.md index 50a4a8cd3..2cb9474b8 100644 --- a/content/intro-to-storybook/vue/zh-CN/conclusion.md +++ b/content/intro-to-storybook/vue/zh-CN/conclusion.md @@ -16,7 +16,7 @@ Storybook 对于 React,React Native,Vue,Angular,Svelte 及其他许多 想要更加深入?这些是一些有用的资源。 -- [**Storybook 官方文档**](https://storybook.js.org/docs/vue/get-started/introduction)拥有 API 文档,社区链接和插件游廊。 +- [**Storybook 官方文档**](https://storybook.js.org/docs/get-started/install)拥有 API 文档,社区链接和插件游廊。 - [**UI 测试手册**](https://storybook.js.org/blog/ui-testing-playbook/)重点介绍了 Twilio,Adobe,Peloton,和 Shopify 高速团队使用的最佳实践。 - [**回归测试手册**](https://storybook.js.org/tutorials/visual-testing-handbook/)深入描述使用 Storybook 对组件进行视觉测试。免费的 31 页电子书。 - [**Storybook Discord 聊天室**](https://discord.gg/UUt2PJb)使您与 Storybook 社区交流。从其他 Storybook 用户获取或提供帮助。 diff --git a/content/intro-to-storybook/vue/zh-CN/data.md b/content/intro-to-storybook/vue/zh-CN/data.md index 122b6c991..d818d22d0 100644 --- a/content/intro-to-storybook/vue/zh-CN/data.md +++ b/content/intro-to-storybook/vue/zh-CN/data.md @@ -201,12 +201,12 @@ import * as TaskStories from './Task.stories'; export default { + component: PureTaskList, + title: 'PureTaskList', + tags: ['autodocs'], decorators: [ () => ({ template: '
' }), ], argTypes: { - onPinTask: {}, - onArchiveTask: {}, + ...TaskStories.ActionsData, }, }; @@ -218,48 +218,52 @@ const Template = (args, { argTypes }) => ({ + template: '', }); -export const Default = Template.bind({}); -Default.args = { - // Shaping the stories through args composition. - // The data was inherited from the Default story in task.stories.js. - tasks: [ - { ...TaskStories.Default.args.task, id: '1', title: 'Task 1' }, - { ...TaskStories.Default.args.task, id: '2', title: 'Task 2' }, - { ...TaskStories.Default.args.task, id: '3', title: 'Task 3' }, - { ...TaskStories.Default.args.task, id: '4', title: 'Task 4' }, - { ...TaskStories.Default.args.task, id: '5', title: 'Task 5' }, - { ...TaskStories.Default.args.task, id: '6', title: 'Task 6' }, - ], +export const Default = { + args: { + // Shaping the stories through args composition. + // The data was inherited from the Default story in Task.stories.js. + tasks: [ + { ...TaskStories.Default.args.task, id: '1', title: 'Task 1' }, + { ...TaskStories.Default.args.task, id: '2', title: 'Task 2' }, + { ...TaskStories.Default.args.task, id: '3', title: 'Task 3' }, + { ...TaskStories.Default.args.task, id: '4', title: 'Task 4' }, + { ...TaskStories.Default.args.task, id: '5', title: 'Task 5' }, + { ...TaskStories.Default.args.task, id: '6', title: 'Task 6' }, + ], + }, }; -export const WithPinnedTasks = Template.bind({}); -WithPinnedTasks.args = { - // Shaping the stories through args composition. - // Inherited data coming from the Default story. - tasks: [ - ...Default.args.tasks.slice(0, 5), - { id: '6', title: 'Task 6 (pinned)', state: 'TASK_PINNED' }, - ], +export const WithPinnedTasks = { + args: { + // Shaping the stories through args composition. + // Inherited data coming from the Default story. + tasks: [ + ...Default.args.tasks.slice(0, 5), + { id: '6', title: 'Task 6 (pinned)', state: 'TASK_PINNED' }, + ], + }, }; -export const Loading = Template.bind({}); -Loading.args = { - tasks: [], - loading: true, +export const Loading = { + args: { + tasks: [], + loading: true, + }, }; -export const Empty = Template.bind({}); -Empty.args = { - // Shaping the stories through args composition. - // Inherited data coming from the Loading story. - ...Loading.args, - loading: false, +export const Empty = { + args: { + // Shaping the stories through args composition. + // Inherited data coming from the Loading story. + ...Loading.args, + loading: false, + }, }; ``` diff --git a/content/intro-to-storybook/vue/zh-CN/deploy.md b/content/intro-to-storybook/vue/zh-CN/deploy.md index c32d6e918..bb2ad7450 100644 --- a/content/intro-to-storybook/vue/zh-CN/deploy.md +++ b/content/intro-to-storybook/vue/zh-CN/deploy.md @@ -15,7 +15,7 @@ commit: '4b1cd77' ## 发布 Storybook -此教程使用 Chromatic,一个 Storybook 维护者提供的免费发布服务。它使得我们可以安全的将我们的 Storybook 部署到云端。 +此教程使用 [Chromatic](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook),一个 Storybook 维护者提供的免费发布服务。它使得我们可以安全的将我们的 Storybook 部署到云端。 ### 在 GitHub 中创建一个仓库 @@ -66,7 +66,7 @@ yarn chromatic --project-token= 执行完成后,您会收到一个已经发布的 Storybook 对应的链接 `https://random-uuid.chromatic.com`。请与您的团队分享链接并获得反馈。 -![Storybook deployed with chromatic package](/intro-to-storybook/chromatic-manual-storybook-deploy-6-0.png) +![Storybook deployed with chromatic package](/intro-to-storybook/chromatic-manual-storybook-deploy.png) 太好了!现在我们只需要一条命令就可以发布我们的 Storybook,但是每次我们需要获取团队关于 UI 的反馈时,我们都要重复的手动执行一次命令。理想情况下,我们希望每次我们提交代码时都可以同步发布最新版本的组件。我们需要持续部署 Storybook。 @@ -89,23 +89,29 @@ on: push # List of jobs jobs: - test: - # Operating System + chromatic: + name: 'Run Chromatic' runs-on: ubuntu-latest # Job steps steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 - run: yarn #👇 Adds Chromatic as a step in the workflow - - uses: chromaui/action@v1 + - uses: chromaui/action@latest # Options required for Chromatic's GitHub Action with: - #👇 Chromatic projectToken, see https://storybook.js.org/tutorials/intro-to-storybook/vue/zh-CN/deploy/ to obtain it + #👇 Chromatic projectToken, see https://storybook.js.org/tutorials/intro-to-storybook/vue/en/deploy/ to obtain it projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }} ``` -

出于文章的简洁起见,GitHub secrets 并没有被提及。Secrets 实际上是 GitHub 提供的安全环境变量,这样我们就不需要硬编码 project-token 了。

+
+ +、出于文章的简洁起见,[GitHub secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository) 并没有被提及。Secrets 实际上是 GitHub 提供的安全环境变量,这样我们就不需要硬编码 `project-token` 了。、 + +
### 提交 action diff --git a/content/intro-to-storybook/vue/zh-CN/get-started.md b/content/intro-to-storybook/vue/zh-CN/get-started.md index 9cf26c05c..e863c7255 100644 --- a/content/intro-to-storybook/vue/zh-CN/get-started.md +++ b/content/intro-to-storybook/vue/zh-CN/get-started.md @@ -32,19 +32,16 @@ yarn 现在通过下述的命令行我们可以快速查看应用程序中的各个环境是否正常运行: ```shell:clipboard=false -# Run the test runner (Jest) in a terminal: -yarn test:unit - # Start the component explorer on port 6006: yarn storybook -# Run the frontend app proper on port 8080: -yarn serve +# Run the frontend app proper on port 5173: +yarn dev ``` 我们的三个前端应用程序模式:自动化测试(Jest),组件开发(Storybook)和应用程序本身。 -![3 modalities](/intro-to-storybook/app-three-modalities-vue.png) +![3 modalities](/intro-to-storybook/app-main-modalities-vue.png) 您可以根据您目前所处理的应用程序的不同部分来决定同时运行上述三个中的一个或者多个。因为我们现在专注于创建一个简单的 UI 组件,所以我们继续运行 Storybook。 diff --git a/content/intro-to-storybook/vue/zh-CN/screen.md b/content/intro-to-storybook/vue/zh-CN/screen.md index df8ec04fa..b1a66dafa 100644 --- a/content/intro-to-storybook/vue/zh-CN/screen.md +++ b/content/intro-to-storybook/vue/zh-CN/screen.md @@ -89,30 +89,19 @@ import App from './App.vue'; 我们同样需要修改 `App` 组件让其渲染 `InboxScreen`(最终,我们将会使用路由来决定渲染哪个页面,现在我们暂时不需要关注这些): -```diff:title=src/App.vue +```html:title=src/App.vue + + - - ``` @@ -130,68 +119,59 @@ import PureInboxScreen from './PureInboxScreen.vue'; export default { component: PureInboxScreen, title: 'PureInboxScreen', + tags: ['autodocs'], }; -const Template = args => ({ - components: { PureInboxScreen }, - setup() { - return { - args, - }; - }, - template: '', -}); +export const Default = {}; -export const Default = Template.bind({}); - -export const Error = Template.bind({}); -Error.args = { error: true }; +export const Error = { + args: { error: true }, +} ``` -我们可以发现尽管 `error` story 运作正常,但因为 `TaskList` 没有连接相对应的 Pinia store,所以 `default` 的 story 出错了。(你同样会遇到类似的问题当试图对 `PureInboxScreen` 进行单元测试时)。 +我们可以发现尽管 `error` story 运作正常,但因为 `TaskList` 没有连接相对应的 Pinia store,所以 `default` 的 story 出错了。 -![Broken inbox](/intro-to-storybook/broken-inboxscreen-vue-pinia.png) +![Broken inbox](/intro-to-storybook/pure-inboxscreen-vue-pinia-tasks-issue.png) 回避此问题的一种方法是永远不要在您应用中渲染容器组件,除非该组件是最高层组件,并且在最高层组件中自顶而下的传递所有需要的数据。 但是,开发人员**将会**不可避免的在下层结构中渲染容器组件。如果我们想要在 Storybook 中渲染应用中大部分或者全部的组件(我们想!),我们仍需要一个解决方案。
-💡 需要说明的是,自顶而下的传递数据是一种合理的解决方案,尤其是使用 GraphQL 时。这也是我们在 Chromatic 中构建超过 800 个 story 的方式。 + +💡 需要说明的是,自顶而下的传递数据是一种合理的解决方案,尤其是使用 [GraphQL](http://graphql.org/) 时。这也是我们在 [Chromatic](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook) 中构建超过 800 个 story 的方式。 +
## 在 story 中提供上下文 -好消息是在 story 中的 `PureInboxScreen` 中使用 Pinia store 十分容易!我们可以更新 story 并直接导入在上一章中创建的 Pinia store。 +好消息是在 story 中使用 Pinia store 十分容易!我们可以更新 `.storybook/preview.js` 配置文件并依赖于Storybook's `setup` 函数以注册我们的 Pinia store。 -```diff:title=src/components/PureInboxScreen.stories.js +```diff:title=.storybook/preview.js + import { setup } from '@storybook/vue3'; + import { createPinia } from 'pinia'; -+ setup((app) => {app.use(createPinia())}); - -import PureInboxScreen from './PureInboxScreen.vue'; - -export default { - title: 'PureInboxScreen', - component: PureInboxScreen, -}; - -const Template = (args) => ({ - components: { PureInboxScreen }, - setup() { - return { - args, - }; +import '../src/index.css'; + +//👇 Registers a global Pinia instance inside Storybook to be consumed by existing stories ++ setup((app) => { ++ app.use(createPinia()); ++ }); + +/** @type { import('@storybook/vue3').Preview } */ +const preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, }, - template: '', -}); - -export const Default = Template.bind({}); +}; -export const Error = Template.bind({}); -Error.args = { error: true }; +export default preview; ``` 在其它库中也存在类似的方法提供模拟上下文,例如 [Apollo](https://www.npmjs.com/package/apollo-storybook-decorator),[Relay](https://github.com/orta/react-storybooks-relay-container) 或者其他库。 @@ -201,7 +181,7 @@ Error.args = { error: true }; @@ -218,57 +198,49 @@ Storybook 的 [`play`](https://storybook.js.org/docs/vue/writing-stories/play-fu play 函数帮助我们验证当 task 更新后 UI 的变化。它使用与框架无关的 DOM API,这意味着不管什么框架,我们都可以通过编写 story 的 play 函数来与 UI 进行交互并模拟人类行为。 -`@storybook/addon-interactions` 帮助我们在 Storybok 中可视化我们的测试,提供一个循序渐进的流程。它还提供了一些方便的 UI 控件,可以暂停、恢复、倒带并逐步完成每个交互。 +`@storybook/addon-interactions` 帮助我们在 Storybook 中可视化我们的测试,提供一个循序渐进的流程。它还提供了一些方便的 UI 控件,可以暂停、恢复、倒带并逐步完成每个交互。 让我们来看看它的实际应用,更新你新创建的 `PureInboxScreen` 文件,并通过添加以下内容来创建组件交互: ```diff:title=src/components/PureInboxScreen.stories.js -import { app } from '@storybook/vue3'; - -+ import { fireEvent, within } from '@storybook/testing-library'; - -import { createPinia } from 'pinia'; - -app.use(createPinia()); - import PureInboxScreen from './PureInboxScreen.vue'; ++ import { fireEvent, within } from '@storybook/test'; + export default { - title: 'PureInboxScreen', component: PureInboxScreen, + title: 'PureInboxScreen', + tags: ['autodocs'], }; -const Template = (args) => ({ - components: { PureInboxScreen }, - setup() { - return { - args, - }; - }, - template: '', -}); - -export const Default = Template.bind({}); +export const Default = {}; -export const Error = Template.bind({}); -Error.args = { error: true }; +export const Error = { + args: { error: true }, +}; -+ export const WithInteractions = Template.bind({}); -+ WithInteractions.play = async ({ canvasElement }) => { -+ const canvas = within(canvasElement); -+ // Simulates pinning the first task -+ await fireEvent.click(canvas.getByLabelText('pinTask-1')); -+ // Simulates pinning the third task -+ await fireEvent.click(canvas.getByLabelText('pinTask-3')); ++ export const WithInteractions = { ++ play: async ({ canvasElement }) => { ++ const canvas = within(canvasElement); ++ // Simulates pinning the first task ++ await fireEvent.click(canvas.getByLabelText('pinTask-1')); ++ // Simulates pinning the third task ++ await fireEvent.click(canvas.getByLabelText('pinTask-3')); ++ }, + }; ``` +
+ +💡 `@storybook/test` 包取代了 `@storybook/jest` 及 `@storybook/testing-library` 测试包,提供了大小近似且基于 [Vitest](https://vitest.dev/) 更清晰的 API 的包。 + +
+ 检查你最新创建的 story。点击 `Interactions` 面板来查看在 story play 函数中的交互列表。 @@ -304,9 +276,9 @@ yarn test-storybook --watch ```
-💡 使用 play 函数的交互测试时测试 UI 组件的绝佳方式。它能做的远比目前看到的多;我们推荐您阅读官方文档进行深入了解。 +💡 使用 play 函数的交互测试时测试 UI 组件的绝佳方式。它能做的远比目前看到的多;我们推荐您阅读 [官方文档](https://storybook.js.org/docs/writing-tests/interaction-testing) 进行深入了解。
-为了深入了解测试,请查看测试手册。它涵盖了缩放前端(scaled-front-end)团队所使用的测试策略,以增强您的开发工作流程。 +为了深入了解测试,请查看 [测试手册](/ui-testing-handbook)。它涵盖了缩放前端(scaled-front-end)团队所使用的测试策略,以增强您的开发工作流程。
![Storybook test runner successfully runs all tests](/intro-to-storybook/storybook-test-runner-execution.png) diff --git a/content/intro-to-storybook/vue/zh-CN/simple-component.md b/content/intro-to-storybook/vue/zh-CN/simple-component.md index f222ef977..9bbbae317 100644 --- a/content/intro-to-storybook/vue/zh-CN/simple-component.md +++ b/content/intro-to-storybook/vue/zh-CN/simple-component.md @@ -54,60 +54,62 @@ export default { 如下所示,我们在 story 文件中创建 Task 的三个不同测试状态: ```js:title=src/components/Task.stories.js +import { fn } from '@storybook/test'; + import Task from './Task.vue'; -import { action } from '@storybook/addon-actions'; +export const ActionsData = { + onPinTask: fn(), + onArchiveTask: fn(), +}; export default { component: Task, + tags: ['autodocs'], //👇 Our exports that end in "Data" are not stories. excludeStories: /.*Data$/, title: 'Task', //👇 Our events will be mapped in Storybook UI argTypes: { - onPinTask: {}, - onArchiveTask: {}, + ...ActionsData }, }; -export const actionsData = { - onPinTask: action('pin-task'), - onArchiveTask: action('archive-task'), -}; - -const Template = args => ({ - components: { Task }, - setup() { - return { args, ...actionsData }; - }, - template: '', -}); -export const Default = Template.bind({}); -Default.args = { - task: { - id: '1', - title: 'Test Task', - state: 'TASK_INBOX', +export const Default = { + args: { + task: { + id: '1', + title: 'Test Task', + state: 'TASK_INBOX', + }, }, }; -export const Pinned = Template.bind({}); -Pinned.args = { - task: { - ...Default.args.task, - state: 'TASK_PINNED', +export const Pinned = { + args: { + task: { + ...Default.args.task, + state: 'TASK_PINNED', + }, }, }; -export const Archived = Template.bind({}); -Archived.args = { - task: { - ...Default.args.task, - state: 'TASK_ARCHIVED', +export const Archived = { + args: { + task: { + ...Default.args.task, + state: 'TASK_ARCHIVED', + }, }, }; ``` +
+ +💡 [**Actions**](https://storybook.js.org/docs/essentials/actions) 可以帮助一在构建独立的 UI 组件时进行交互验证。一般来说,你无法访问程序上下文中的函数及状态。使用 `fn()` 可以做到。 + +
+ Storybook 有两个基本的组织级别:组件和他的 story。可以将每个 story 视作其组件的排列组合。您可以根据需要给每一个组件创建任意个 story。 - **组件** @@ -122,30 +124,21 @@ Storybook 有两个基本的组织级别:组件和他的 story。可以将每 - `component` -- 组件本身 - `title` -- 如何在 Storybook 应用侧边栏中引用组件 +- `tags` -- 自动为我们的组件生成文档 - `excludeStories` -- story 本身需要但是不用在 Storybook 应用中渲染的信息 -- `argTypes` -- 在每个 story 中具体说明 [args](https://storybook.js.org/docs/vue/api/argtypes) 的行为 - -为了定义我们的 stories,我们为每个测试状态导出一个函数用于生成一个 story。Story 实际上就是一个根据给定的状态返回已渲染元素(例如:一个具有一组 props 的类组件)的函数---就像是[函数式组件](https://vuejs.org/v2/guide/render-function.html#Functional-Components)。 +- `args` -- 在每个 story 中具体说明 [args](https://storybook.js.org/docs/essentials/actions#action-args) 的行为 -因为我们的组件存在多种排列组合,所以设置一个 `Template` 变量不失为一种便捷的做法。使用这样的模式来创建您的 Story 可以大量减少代码量和维护成本。 - -
-💡 Template.bind({})标准JavaScript 中用来复制函数的技术。我们使用这项技术保证了在使用同一份实现的同时,让每一个导出的story可以配置自己的属性。 -
+为了定义我们的 stories,我们将使用 Component Story Format 3 (也即 [CSF3](https://storybook.js.org/docs/api/csf) )构建所有的测试用例。这种形式被设计用来以一种简洁的方式构建我们所用到的测试用例。通过导出包含每个组件状态的对象,我们可以更直观地定义测试,并更有效地创建和复用 story。 Arguments 或者简写 [`args`](https://storybook.js.org/docs/vue/writing-stories/args) ,让我们可以在不重启 Storybook 的前提下实时编辑我们的组件。一旦 [`args`](https://storybook.js.org/docs/vue/writing-stories/args) 的值被修改我们的组件也会进行相应的更新。 -当创建一个 story,我们使用一个基本的 `task` 变量来构建 task 组件所期望的形状。通常是根据真实数据来进行建模的。 - -`actions` 允许我们创建 Storybook UI 的 **actions** 面板被点击时显示的回调。因此当我们构建一个 pin button 时,我们能够在 UI 上验证 button 点击是否成功。 +`fn()` 允许我们创建 Storybook UI 的 **actions** 面板被点击时显示的回调。因此当我们构建一个 pin button 时,我们能够在 UI 上验证 button 点击是否成功。 由于我们需要将相同的一组 actions 传入到组件的所有排列组合中,将它们合并到一个 `actionsData` 变量中,并在我们每次定义 story 的时候传入将会变得非常方便。 值得一提的是当我们将组件所需的操作都合并到 `actionsData` 之后,我们可以在其他组件复用此组件时,让其他组件的 story 也可以复用 `export` 的 `actionsData`,详见下文。 -
-💡 Actions 帮助您在独立构建UI组件时验证交互。通常情况下您无法访问应用程序上下文中的函数和状态。请使用 action() 将他们插入。 -
+当创建一个 story 时,我们使用一个基本的 `task` 变量来构建 task 组件所期望的形状。通常是根据真实数据来进行建模的。再次说明,`export`-ing 这个语法将让我们在之后的 story 中复用,之后将会看到。 ## 配置 @@ -154,11 +147,9 @@ Arguments 或者简写 [`args`](https://storybook.js.org/docs/vue/writing-storie 首先,修改您的 Storybook 配置文件(`.storybook/main.js`) 为以下内容: ```diff:title=.storybook/main.js -module.exports = { -- stories: [ -- '../src/**/*.stories.mdx', -- '../src/**/*.stories.@(js|jsx|ts|tsx)' -- ], +/** @type { import('@storybook/vue3-vite').StorybookConfig } */ +const config = { +- stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], + stories: ['../src/components/**/*.stories.js'], staticDirs: ['../public'], addons: [ @@ -166,14 +157,12 @@ module.exports = { '@storybook/addon-essentials', '@storybook/addon-interactions', ], - framework: '@storybook/vue3', - core: { - builder: '@storybook/builder-webpack5', - }, - features: { - interactionsDebugger: true, + framework: { + name: '@storybook/vue3-vite', + options: {}, }, }; +export default config; ``` 完成上述的修改后,修改位于 `.storybook`文件夹中的 `preview.js` 为以下内容: @@ -182,26 +171,28 @@ module.exports = { + import '../src/index.css'; //👇 Configures Storybook to log the actions( onArchiveTask and onPinTask ) in the UI. -export const parameters = { - actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, +/** @type { import('@storybook/vue3').Preview } */ +const preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, }, }, }; -``` -[`parameters`](https://storybook.js.org/docs/vue/writing-stories/parameters) 通常用来控制 Storybook 功能和插件的行为。在我们的例子中,我们使用它们来配置 `actions` (模拟回调)如何被处理。 +export default preview; +``` -`actions` 允许我们创建 Storybook UI 的 **actions** 面板被点击时显示的回调。因此当我们构建一个 pin button 时,我们能够在 UI 上验证 button 点击是否成功。 +[`parameters`](https://storybook.js.org/docs/vue/writing-stories/parameters) 通常用来控制 Storybook 功能和插件的行为。在我们的例子中,我们并不需要用到这个能力。相反,我们将会导入我们应用的 CSS 文件。 当我们完成这些,重启 Storybook 服务将会生成三种 Task 状态的测试用例: @@ -301,7 +292,7 @@ export default { @@ -327,6 +318,7 @@ yarn add --dev @storybook/addon-a11y 然后,更新 Storybook 配置文件(`.storybook/main.js`)来启用它: ```diff:title=.storybook/main.js +/** @type { import('@storybook/vue3-vite').StorybookConfig } */ module.exports = { stories: ['../src/components/**/*.stories.js'], staticDirs: ['../public'], @@ -336,17 +328,16 @@ module.exports = { '@storybook/addon-interactions', + '@storybook/addon-a11y', ], - framework: '@storybook/vue3', - core: { - builder: '@storybook/builder-webpack5', - }, - features: { - interactionsDebugger: true, + framework: { + name: '@storybook/vue3-vite', + options: {}, }, }; +export default config; ``` +最终,重启你的 Storybook 服务,就可以在 UI 中看到新的插件。 -![Task accessibility issue in Storybook](/intro-to-storybook/finished-task-states-accessibility-issue.png) +![Task accessibility issue in Storybook](/intro-to-storybook/finished-task-states-accessibility-issue-7-0.png) 回顾我们的 stories,我们可以发现插件在我们的一个测试状态中发现可访问性问题。 [**"Elements must have sufficient color contrast"**](https://dequeuniversity.com/rules/axe/4.4/color-contrast?application=axeAPI) 信息实质上意味着在 task 标题和背景之间没有足够的差异性。我们可以快速的修复这个问题,通过修改应用程序的 CSS(位于`src/index.css`),将文本颜色改为 darker gray 。 diff --git a/content/intro-to-storybook/vue/zh-CN/test.md b/content/intro-to-storybook/vue/zh-CN/test.md index dd864136a..1313e1c61 100644 --- a/content/intro-to-storybook/vue/zh-CN/test.md +++ b/content/intro-to-storybook/vue/zh-CN/test.md @@ -86,7 +86,7 @@ git checkout -b change-task-background 这为每个 task 项提供了一个新的背景色。 -![task background change](/intro-to-storybook/chromatic-task-change.png) +![task background change](/intro-to-storybook/chromatic-task-change-7-0.png) 添加文件: diff --git a/content/intro-to-storybook/vue/zh-CN/using-addons.md b/content/intro-to-storybook/vue/zh-CN/using-addons.md index b9eb95a95..0d259c316 100644 --- a/content/intro-to-storybook/vue/zh-CN/using-addons.md +++ b/content/intro-to-storybook/vue/zh-CN/using-addons.md @@ -5,11 +5,11 @@ description: '学习如何集成并使用热门插件' commit: '13da85f' --- -Storybook 拥有一个健壮的[插件](https://storybook.js.org/docs/vue/configure/storybook-addons)生态系统来帮助您的团队提高开发体验。在[这里](https://storybook.js.org/addons)查看它们, +Storybook 拥有一个健壮的[插件](https://storybook.js.org/docs/configure/storybook-addons)生态系统来帮助您的团队提高开发体验。在[这里](https://storybook.js.org/integrations)查看它们, 如果您完成了教程之前的部分,您实际上已经接触了一些插件,并在[测试](/intro-to-storybook/vue/zh-CN/test/)章节配置了其中一个。 -基本上每一个用例都有对应的插件,要把它们都写出来可能要花很长时间。让我们来集成其中最受欢迎的一个吧:[Controls](https://storybook.js.org/docs/vue/essentials/controls)。 +基本上每一个用例都有对应的插件,要把它们都写出来可能要花很长时间。让我们来集成其中最受欢迎的一个吧:[Controls](https://storybook.js.org/docs/essentials/controls)。 ## 什么是 Controls? @@ -19,7 +19,7 @@ Controls 允许设计人员和开发人员通过*修改*组件参数的方式轻 @@ -32,7 +32,7 @@ Storybook 是一个非常棒的[组件驱动式开发环境](https://www.compone 通过 Controls,任何的 QA 工程师,UI 工程师,或者其他的利益相关者都可以将组件推进到极致!让我们考虑下面这个例子,如果我们添加了一个**非常长**的字符串,我们的 `Task` 会发生什么? -![Oh no! The far right content is cut-off!](/intro-to-storybook/task-edge-case.png) +![Oh no! The far right content is cut-off!](/intro-to-storybook/task-edge-case-non-react.png) 这显然不对!就好像文本从 Task 组件的边界溢出了一样。 @@ -81,7 +81,7 @@ Controls 使得我们可以快速验证组件的各种输入。在这个例子 ``` -![That's better.](/intro-to-storybook/edge-case-solved-with-controls.png) +![That's better.](/intro-to-storybook/edge-case-solved-controls-non-react.png) 问题解决了!当文本到达 Task 区域边界时,通过漂亮的省略号将其截断。 @@ -107,14 +107,18 @@ LongTitle.args = { 如果我们在进行[视觉测试](/intro-to-storybook/vue/zh-CN/test/),那么在截断方案失效时我们也会得到提示。如果没有覆盖测试,这样的模糊的边缘用例很容易被忽视。 -

💡 Controls 非常适合让一些非开发人员测试您的组件和 story,它远比您想象的要强大。我们推荐您阅读官方文档来了解更多。此外您还可以使用很多别的方式来定制 Storybook,以通过插件来适应您的工作流程。在创建组件指导中我们将会教您,通过创建一个插件来帮助增强您的开发工作流程。

+
+ +Controls 非常适合让一些非开发人员测试您的组件和 story,它远比您想象的要强大。我们推荐您阅读[官方文档](https://storybook.js.org/docs/essentials/controls) 来了解更多。此外您还可以使用很多别的方式来定制 Storybook,以通过插件来适应您的工作流程。在[创建组件指导](https://storybook.js.org/docs/addons/writing-addons)中我们将会教您,通过创建一个插件来帮助增强您的开发工作流程。 + +
### 合并修改