diff --git a/content/intro-to-storybook/react/ja/composite-component.md b/content/intro-to-storybook/react/ja/composite-component.md index 127172d7b..8f579b92e 100644 --- a/content/intro-to-storybook/react/ja/composite-component.md +++ b/content/intro-to-storybook/react/ja/composite-component.md @@ -9,21 +9,21 @@ commit: '429780a' ## TaskList (タスクリスト) -Taskbox はピン留めされたタスクを通常のタスクより上部に表示することで強調します。これにより `TaskList` に、タスクのリストが、通常のタスクのみである場合と、ピン留めされたタスクとの組み合わせである場合という、ストーリーを追加するべき 2 つのバリエーションができます。 +Taskbox はピン留めされたタスクを通常のタスクより上部に表示することで強調します。これにより `TaskList` に、タスクのリストが通常のタスクのみである場合と、ピン留めされたタスクとの組み合わせである場合というストーリーを追加するべき 2 つのバリエーションができます。 ![通常のタスクとピン留めされたタスク](/intro-to-storybook/tasklist-states-1.png) -`Task` のデータは非同期的に送信されるので、接続がないことを示すため、読み込み中の状態**も**必要となります。さらにタスクがない場合に備え、空の状態も必要です。 +`Task` のデータは非同期に送信されるので、接続がないことを示すため、読み込み中の状態**も併せて**必要となります。さらにタスクがない場合に備え、空の状態も必要です。 ![空の状態と読み込み中の状態](/intro-to-storybook/tasklist-states-2.png) ## セットアップする -複合的なコンポーネントも基本的なコンポーネントと大きな違いはありません。`TaskList` のコンポーネントとそのストーリーファイル、`src/components/TaskList.js` と `src/components/TaskList.stories.js` を作成しましょう。 +複合的なコンポーネントも基本的なコンポーネントと大きな違いはありません。`TaskList` のコンポーネントとそのストーリーファイル、`src/components/TaskList.jsx` と `src/components/TaskList.stories.jsx` を作成しましょう。 まずは `TaskList` の大まかな実装から始めます。前の章で作成した `Task` コンポーネントをインポートし、属性とアクションを入力として渡します。 -```js:title=src/components/TaskList.js +```jsx:title=src/components/TaskList.jsx import React from 'react'; import Task from './Task'; @@ -54,61 +54,61 @@ export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { 次に `Tasklist` のテスト状態をストーリーファイルに記述します。 -```js:title=src/components/TaskList.stories.js -import React from 'react'; - +```jsx:title=src/components/TaskList.stories.jsx import TaskList from './TaskList'; + import * as TaskStories from './Task.stories'; export default { component: TaskList, title: 'TaskList', - decorators: [story =>
{story()}
], + decorators: [(story) =>
{story()}
], + tags: ['autodocs'], }; -const Template = args => ; - -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.jsx. + 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: { + 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 というキーをデフォルトエクスポートに追加し、描画するコンポーネントの周りに padding を設定してます。ストーリーで使用する「プロバイダー」(例えば、React のコンテキストを設定するライブラリコンポーネントなど) を使うためにも使用します。 +💡 デコレーターを使ってストーリーに任意のラッパーを設定できます。上記のコードでは、decorators というキーをデフォルトエクスポートに追加し、描画するコンポーネントの周りに padding を設定しています。ストーリーで使用する「プロバイダー」(例えば、React のコンテキストを設定するライブラリコンポーネントなど) を使うためにも使用します。
`TaskStories` をインポートすることで、ストーリーに必要な引数 (args) を最小限の労力で[組み合わせる](https://storybook.js.org/docs/react/writing-stories/args#args-composition)ことができます。そうすることで、2 つのコンポーネントが想定するデータとアクション (呼び出しのモック) の一貫性が保たれます。 @@ -117,16 +117,16 @@ Empty.args = { ## 状態を作りこむ -今のコンポーネントはまだ粗削りですが、ストーリーは見えています。単に `.list-items` だけのためにラッパーを作るのは単純すぎると思うかもしれません。実際にその通りです。ほとんどの場合単なるラッパーのためだけに新しいコンポーネントは作りません。`TaskList` の**本当の複雑さ**は `withPinnedTasks`、`loading`、`empty` といったエッジケースに現れているのです。 +今のコンポーネントはまだ粗削りですが、ストーリーは見えています。単に `.list-items` だけのためにラッパーを作るのは単純すぎると思うかもしれません。実際、その通りです。ほとんどの場合、単なるラッパーのためだけに新しいコンポーネントは作りません。`TaskList` の**本当の複雑さ**は `withPinnedTasks`、`loading`、`empty` といったエッジケース(ユーザーが遭遇する可能性のあるまれなバグ)に現れているのです。 -```js:title=src/components/TaskList.js +```jsx:title=src/components/TaskList.jsx import React from 'react'; import Task from './Task'; @@ -161,16 +161,16 @@ export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) {
-
You have no tasks
-
Sit back and relax
+

You have no tasks

+

Sit back and relax

); } const tasksInOrder = [ - ...tasks.filter((t) => t.state === "TASK_PINNED"), - ...tasks.filter((t) => t.state !== "TASK_PINNED"), + ...tasks.filter((t) => t.state === 'TASK_PINNED'), + ...tasks.filter((t) => t.state !== 'TASK_PINNED'), ]; return (
@@ -182,11 +182,11 @@ export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { } ``` -追加したマークアップで UI は以下のようになります: +追加したマークアップで UI は以下のようになります。 @@ -197,14 +197,60 @@ export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { コンポーネントが大きくなるにつれ、入力の要件も増えていきます。`TaskList` のプロパティの要件を定義しましょう。`Task` が子供のコンポーネントなので、`Task` を表示するのに正しいデータ構造が渡されていることを確認しましょう。時間を節約するため、前の章で `Task` に定義した `propTypes` を再利用しましょう。 -```diff:title=src/components/TaskList.js +```diff:title=src/components/TaskList.jsx import React from 'react'; + import PropTypes from 'prop-types'; import Task from './Task'; export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { - ... + const events = { + onPinTask, + onArchiveTask, + }; + const LoadingRow = ( +
+ + + Loading cool state + +
+ ); + if (loading) { + return ( +
+ {LoadingRow} + {LoadingRow} + {LoadingRow} + {LoadingRow} + {LoadingRow} + {LoadingRow} +
+ ); + } + if (tasks.length === 0) { + return ( +
+
+ +

You have no tasks

+

Sit back and relax

+
+
+ ); + } + + const tasksInOrder = [ + ...tasks.filter((t) => t.state === 'TASK_PINNED'), + ...tasks.filter((t) => t.state !== 'TASK_PINNED'), + ]; + return ( +
+ {tasksInOrder.map((task) => ( + + ))} +
+ ); } + TaskList.propTypes = { diff --git a/content/intro-to-storybook/react/ja/conclusion.md b/content/intro-to-storybook/react/ja/conclusion.md index 747563349..03233cd3d 100644 --- a/content/intro-to-storybook/react/ja/conclusion.md +++ b/content/intro-to-storybook/react/ja/conclusion.md @@ -5,30 +5,30 @@ description: '今までの知識をまとめて、Storybook のテクニック Storybook で最初の UI を作成しましたね。お疲れ様でした!ここまでの章で UI コンポーネントを作成し、複合させ、テストし、デプロイする方法を学びました。同じように進めていれば、リポジトリーとデプロイされた Storybook は以下のリンクと同じようになっていることでしょう。 -[📕 **GitHub のリポジトリー: chromaui/learnstorybook-code**](https://github.com/chromaui/learnstorybook-code) +[📕 **GitHub のリポジトリ: chromaui/learnstorybook-code**](https://github.com/chromaui/learnstorybook-code)
[🌎 **デプロイされた Storybook**](https://master--5ccbe484c994280020b6d128.chromatic.com) -Storybook は React、React Native、Vue、Angular、Svelte、その他のフレームワークにとって強力なツールです。開発者コミュニティーも活発でアドオンも充実しています。このチュートリアルで紹介した内容は、Storybook で出来ることの一部にすぎません。一度 Storybook を導入すれば、強固な UI を効率的に作れることに驚くことでしょう。 +Storybook は React、React Native、Vue、Angular、Svelte、その他のフレームワークにとって強力なツールです。開発者コミュニティーも活発でアドオンも充実しています。このチュートリアルで紹介した内容は、Storybook でできることのほんの一部にすぎません。一度 Storybook を導入すれば、強固な UI を効率的に作れることにきっと驚くことでしょう。 ## さらに学ぶには -もっと深く掘り下げたいですか?役に立つリソースを紹介します。 +もっと深く掘り下げたい方のために役に立つリソースを紹介します。 - [**Storybook の公式ドキュメント**](https://storybook.js.org/docs/react/get-started/introduction)には API ドキュメント、コミュニティのリンク、アドオンのギャラリーがあります。 -- [**楽しい Storybook のワークフロー**](https://www.chromatic.com/blog/the-delightful-storybook-workflow)では Squarespace や、メジャーリーグサッカー、ディスカバリーネットワーク、 Apollo GraphQL といった効率の良いチームにおけるワークフローのベストプラクティスを紹介しています。 +- [**楽しい Storybook のワークフロー**](https://www.chromatic.com/blog/the-delightful-storybook-workflow)では Twilio、Adobe、Peloton、Shopifyといった効率の良いチームにおけるワークフローのベストプラクティスを紹介しています。 -- [**視覚的なテストのハンドブック**](https://storybook.js.org/tutorials/visual-testing-handbook)では、コンポーネントを Storybook で視覚的にテストする方法を掘り下げています。無料の 31 ページある eBook です。 +- [**視覚的なテストのハンドブック (Visual Testing Handbook)**](https://storybook.js.org/tutorials/visual-testing-handbook)では、コンポーネントを Storybook で視覚的にテストする方法を掘り下げています。無料の 31 ページある eBook です。 -- [**Storybook Discord**](https://discord.gg/UUt2PJb) では Storybook のコミュニティに参加できます。他の Storybook ユーザーと協力しましょう。 +- [**Storybook Discord**](https://discord.gg/UUt2PJb) ではStorybook のコミュニティに参加できます。他の Storybook ユーザーと協力しましょう。 - [**Storybook ブログ**](https://storybook.js.org/blog)ではリリース情報や、UI 開発のワークフローを合理的にするための機能を紹介します。 ## 誰が Intro to Storybook チュートリアルを作成しているのでしょうか? -文書や、コード、製作は [Chromatic](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook) の貢献です。このチュートリアルは Chromatic の [GraphQL + React tutorial series](https://www.chromatic.com/blog/graphql-react-tutorial-part-1-6) を参考にしています。 +文書や、コード、製作は [Chromatic](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook) の貢献によるものです。このチュートリアルは Chromatic の [GraphQL + React tutorial series](https://www.chromatic.com/blog/graphql-react-tutorial-part-1-6) を参考にしています。 -このようなチュートリアルや記事をお望みならば、Storybook のメーリングリストにサインアップしてください。 +このようなチュートリアルや記事をさらに読みたい場合は、Storybook のメーリングリストに登録することをオススメします。 diff --git a/content/intro-to-storybook/react/ja/contribute.md b/content/intro-to-storybook/react/ja/contribute.md index 8e01b35b7..f868d7576 100644 --- a/content/intro-to-storybook/react/ja/contribute.md +++ b/content/intro-to-storybook/react/ja/contribute.md @@ -3,10 +3,10 @@ title: '貢献する' description: 'Storybook を世界に広めましょう' --- -Learn Storybook に協力してください!文法や句読点など小さなことなら、プルリクエストを送ってください。もし大きなことなら [GitHub の issue を追加](https://github.com/chromaui/learnstorybook.com/issues)して議論しましょう。 +このチュートリアルの作成にご協力ください!文法や句読点などの些細な問題であれば、プルリクエストを送ってください。もし大規模な問題であれば、 [GitHub の issue を追加](https://github.com/chromaui/learnstorybook.com/issues)して議論しましょう。 -Learn Storybook はコミュニティによって作成・運営されていますので、最新の状態を保ち、荒い部分を削るには皆さんの協力が必要です。どんな協力でも歓迎します。 +StoryBookのチュートリアルはコミュニティによって作成・運営されていますので、最新の状態を保ち、荒い部分を削るには皆さんのご協力が不可欠です。どのような協力でも歓迎します。 ## 翻訳 -Storybook をすべての人が使用できるように、このチュートリアルを他の言語に翻訳するのを手伝ってください。中国語やスペイン語は特に歓迎します。興味があれば[この issue](https://github.com/chromaui/learnstorybook.com/issues/3) にコメントしてください。 +Storybook をすべての人が使用できるように、このチュートリアルの他言語への翻訳にお力を貸してください。中国語やスペイン語はとくに歓迎します。ご興味があれば[この issue](https://github.com/chromaui/learnstorybook.com/issues/3) にコメントしてください。 diff --git a/content/intro-to-storybook/react/ja/data.md b/content/intro-to-storybook/react/ja/data.md index f48bb0e9d..0ef3d6feb 100644 --- a/content/intro-to-storybook/react/ja/data.md +++ b/content/intro-to-storybook/react/ja/data.md @@ -13,15 +13,15 @@ commit: 'c70ec15' `TaskList` コンポーネントは、今のところ「presentational (表示用)」として書かれており、その実装以外の外部とは何もやりとりをしません。データを中に入れるためにはデータプロバイダに繋ぐ必要があります。 -ここではデータを保存する際に使用される React で人気のライブラリーである [Redux](https://redux.js.org/) を使用し、アプリケーションにシンプルなデータモデルを作ります。[Apollo](https://www.apollographql.com/client/) や [MobX](https://mobx.js.org/) といった他のデータ管理用のライブラリーでもここでのパターンが使用できます。 +ここでは、[Redux](https://redux.js.org/) でデータを保存するためにもっとも効果的な開発用ツールセットである [Redux Toolkit](https://redux-toolkit.js.org/)を使用し、アプリケーションにシンプルなデータモデルを作ります。[Apollo](https://www.apollographql.com/client/) や [MobX](https://mobx.js.org/) といった他のデータ管理用のライブラリーでもここでのパターンが使用できます。 -以下のコマンドを実行し必要な依存関係を追加しましょう: +以下のコマンドを実行し必要な依存関係を追加しましょう。 ```shell yarn add @reduxjs/toolkit react-redux ``` -まず、タスクの状態を変更するアクションを処理する単純な Redux のストアを作ります。`src/lib` フォルダの `store.js` というファイルを作ってください (あえて簡単にしています): +まず、タスクの状態を変更するアクションを処理する単純な Redux のストアを作ります。`src/lib` フォルダの `store.js` というファイルを作ってください (あえて簡単にしています)。 ```js:title=src/lib/store.js /* A simple redux store/actions/reducer implementation. @@ -81,9 +81,9 @@ const store = configureStore({ export default store; ``` -次に、`TaskList` コンポーネントのデフォルトエクスポートを更新し、Redux のストアに 「connect (接続)」し、ストアから、気になるタスクのリストを描画します。 +次に、`TaskList` コンポーネントのデフォルトエクスポートを更新し、Redux のストアに「connect (接続)」し、ストアから気になるタスクのリストを描画します。 -```js:title=src/components/TaskList.js +```jsx:title=src/components/TaskList.jsx import React from 'react'; import Task from './Task'; import { useDispatch, useSelector } from 'react-redux'; @@ -139,8 +139,8 @@ export default function TaskList() {
-
You have no tasks
-
Sit back and relax
+

You have no tasks

+

Sit back and relax

); @@ -161,21 +161,19 @@ export default function TaskList() { } ``` -これで、Redux からデータを取得し、実際のデータでコンポ―ネントを生成できるようになりました。`src/app.js` に接続してコンポーネントを描画することも可能ですが、今のところはこのままにして、コンポーネント駆動の旅を続けましょう。 +これで、Redux からデータを取得し、実際のデータでコンポーネントを生成できるようになりました。`src/app.js` に接続してコンポーネントを描画することも可能ですが、今のところはこのままにして、コンポーネント駆動の旅を続けましょう。 -アプリケーションで表示する方法は次の章で説明しますのでご心配なく。 +アプリケーションで表示する方法は次の章で説明しますので心配ありません。 ## デコレーターにコンテキストを渡す この段階で、Storybook のテストが動かなくなりました。`TaskList` が繋がれたコンポーネントとなって、タスクを取得しアップデートするのに Redux ストアに依存しているからです。 -![壊れたタスクリスト](/intro-to-storybook/broken-tasklist-optimized.png) +![壊れたタスクリスト](/intro-to-storybook/broken-tasklist-7-0-optimized.png) -この問題を解決するために、さまざまなアプローチができます。しかし、私たちのアプリは非常に単純なので、[前の章](/intro-to-storybook/react/ja/composite-component)で行ったのと同様に、デコレーターに頼ることができ、Storybook の中でモックストアを利用できます: - -```js:title=src/components/TaskList.stories.js -import React from 'react'; +この問題を解決するために、さまざまなアプローチができます。しかし、このアプリは非常に単純なので、[前の章](/intro-to-storybook/react/ja/composite-component)で行ったのと同様にデコレーターに頼ることができ、Storybook の中でモックストアを利用できます。 +```jsx:title=src/components/TaskList.stories.jsx import TaskList from './TaskList'; import * as TaskStories from './Task.stories'; @@ -225,80 +223,83 @@ const Mockstore = ({ taskboxState, children }) => ( export default { component: TaskList, title: 'TaskList', - decorators: [(story) =>
{story()}
], + decorators: [(story) =>
{story()}
], + tags: ['autodocs'], excludeStories: /.*MockedState$/, }; -const Template = () => ; - -export const Default = Template.bind({}); -Default.decorators = [ - (story) => {story()}, -]; +export const Default = { + decorators: [ + (story) => {story()}, + ], +}; -export const WithPinnedTasks = Template.bind({}); -WithPinnedTasks.decorators = [ - (story) => { - const pinnedtasks = [ - ...MockedState.tasks.slice(0, 5), - { id: '6', title: 'Task 6 (pinned)', state: 'TASK_PINNED' }, - ]; +export const WithPinnedTasks = { + decorators: [ + (story) => { + const pinnedtasks = [ + ...MockedState.tasks.slice(0, 5), + { id: '6', title: 'Task 6 (pinned)', state: 'TASK_PINNED' }, + ]; + + return ( + + {story()} + + ); + }, + ], +}; - return ( +export const Loading = { + decorators: [ + (story) => ( {story()} - ); - }, -]; - -export const Loading = Template.bind({}); -Loading.decorators = [ - (story) => ( - - {story()} - - ), -]; + ), + ], +}; -export const Empty = Template.bind({}); -Empty.decorators = [ - (story) => ( - - {story()} - - ), -]; +export const Empty = { + decorators: [ + (story) => ( + + {story()} + + ), + ], +}; ```
-💡 excludeStories は Storybook の設定のフィールドで、モックされた状態がストーリーとして扱われるのを防ぐためのものです。このフィールドについては Storybook documentation で詳しく説明されています。 +💡 excludeStories は Storybook の設定のフィールドで、モックされた状態がストーリーとして扱われるのを防ぐためのものです。このフィールドについては ドキュメント で詳しく説明されています。
-💡 この変更により、全てのテストはアップデートが必要になります。-u フラグをつけてテストを再実行し、アップデートしてください。 Git へのコミットを忘れずに行ってください! +💡 Git へのコミットを忘れずに行ってください!
成功です! Storybook が動作し、接続されたコンポーネントにデータを渡す方法を確認することができました。次の章では、ここで学んだことを画面に適用してみましょう。 diff --git a/content/intro-to-storybook/react/ja/deploy.md b/content/intro-to-storybook/react/ja/deploy.md index 94ba7cf97..b0712f465 100644 --- a/content/intro-to-storybook/react/ja/deploy.md +++ b/content/intro-to-storybook/react/ja/deploy.md @@ -11,27 +11,27 @@ commit: '59da1ac' Storybook をデプロイするには、まず静的サイトとしてエクスポートします。この機能はすでに組み込まれて、使える状態となっているので、設定について気にする必要はありません。 -`yarn build-storybook` を実行すると、`storybook-static` ディレクトリーに Storybook が静的サイトとして出力されますので、静的サイトのホスティングサービスのデプロイ出来ます。 +`yarn build-storybook` を実行すると、`storybook-static` ディレクトリに Storybook が静的サイトとして出力されますので、静的サイトのホスティングサービスにデプロイできます。 -## Storybook を発行する +## Storybook を公開する -このチュートリアルでは、Storybook のメンテナーが作成した、無料のホスティングサービスである Chromatic を使用します。Chromatic を使えば、クラウド上に Storybook を安全に、デプロイしホストすることができます。 +このチュートリアルでは、Storybook のメンテナーが作成した、無料のホスティングサービスである Chromatic を使用します。Chromatic を使えば、クラウド上に Storybook を安全にデプロイし、またホストできます。 -### GitHub にリポジトリーを作成する +### GitHub にリポジトリを作成する -デプロイの前に、リモートのバージョン管理サービスへローカルのコードを同期しなければなりません。[はじめにの章](/intro-to-storybook/react/ja/get-started/)で Create React App (CRA) でプロジェクトを初期化した際に、ローカルのリポジトリーはすでに作成されています。また、この段階でリモートリポジトリーにプッシュできるコミットがあるはずです。 +デプロイの前に、リモートのバージョン管理サービスへローカルのコードを同期しなければなりません。[はじめの章](/intro-to-storybook/react/ja/get-started/)でプロジェクトを初期化した際に、ローカルのリポジトリはすでに作成されています。この段階に来れば、リモートリポジトリにプッシュできるコミットがあるはずです。 -[ここから](https://github.com/new) GitHub にアクセスし、リポジトリーを作りましょう。リポジトリーの名前はローカルと同じく「taskbox」とします。 +[ここから](https://github.com/new) GitHub にアクセスし、リポジトリを作りましょう。リポジトリの名前はローカルと同じく「taskbox」とします。 ![GitHub のセットアップ](/intro-to-storybook/github-create-taskbox.png) -新しいリポジトリーを作ったら origin の URL をコピーして、次のコマンドを実行し、ローカルの Git プロジェクトにリモートを追加します: +新しいリポジトリを作ったら origin の URL をコピーして、次のコマンドを実行し、ローカルの Git リポジトリを GitHub のリモートリポジトリに追加します。 ```shell -git remote add origin https://github.com//taskbox.git +git remote add origin https://github.com//taskbox.git ``` -最後にローカルリポジトリーを GitHub のリモートリポジトリーにプッシュします: +最後にローカルリポジトリを GitHub のリモートリポジトリにプッシュします。 ```shell git push -u origin main @@ -45,9 +45,9 @@ git push -u origin main yarn add -D chromatic ``` -パッケージをインストールしたら、GitHub のアカウントを使用して [Chromatic にログイン](https://www.chromatic.com/start/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook)します。(Chromatic は一部のアクセス許可を要求します。) 「taskbox」という名前でプロジェクトを作成し、GitHub のリポジトリーと同期させます。 +パッケージをインストールしたら、GitHub のアカウントを使用して [Chromatic にログイン](https://www.chromatic.com/start/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook)します。(Chromatic は一部のアクセス許可を要求します。)「taskbox」という名前でプロジェクトを作成し、GitHub のリポジトリと同期させます。 -ログインしたら `Choose from GitHub` をクリックし、リポジトリーを選択します。 +ログインしたら `Choose GitHub repo` をクリックし、リポジトリを選択します。
-それでは、アプリケーションのさまざまな環境が問題なく動くことを次のコマンドで確認しましょう: +それでは、アプリケーションのさまざまな環境が問題なく動くことを次のコマンドで確認しましょう。 ```shell:clipboard=false -# Run the test runner (Jest) in a terminal: -yarn test --watchAll - # Start the component explorer on port 6006: yarn storybook -# Run the frontend app proper on port 3000: -yarn start +# Run the frontend app proper on port 5173: +yarn dev ``` -
-テストのコマンドに --watchAll フラグを付けているのに気づいたでしょうか。これは間違いではありません。このフラグを付けることにより、すべてのテストが実行され、アプリケーションに問題ないことを確実にできます。チュートリアルを進めると、別のテストシナリオも出てきます。必要ならばこのフラグを package.json のテストコマンドに追加することで、テストスイートのすべてのテストが実行されるようになります。 -
- -フロントエンド開発の 3 つのモード: 自動化されたテスト (Jest)、コンポーネント開発 (Storybook)、アプリケーション自体 +フロントエンド開発の 2 つのモード: コンポーネント開発 (Storybook)、アプリケーション自体 -![3 つのモード](/intro-to-storybook/app-three-modalities.png) +![2つのモード](/intro-to-storybook/app-main-modalities-react.png) 作業をする対象に応じて、このモードのうち 1 つまたは複数を同時に動かしながら作業します。今は単一の UI コンポーネントを作るのに集中するため、Storybook を動かすことにしましょう。 @@ -66,7 +59,7 @@ git init git add . ``` -次に以下を実行します: +さらに: ```shell git commit -m "first commit" @@ -75,7 +68,7 @@ git commit -m "first commit" 最後に: ```shell -$ git branch -M main +git branch -M main ``` それでは最初のコンポーネントを作り始めましょう! diff --git a/content/intro-to-storybook/react/ja/screen.md b/content/intro-to-storybook/react/ja/screen.md index 67cd29a67..220cadf74 100644 --- a/content/intro-to-storybook/react/ja/screen.md +++ b/content/intro-to-storybook/react/ja/screen.md @@ -5,7 +5,7 @@ description: 'コンポーネントをまとめて画面を作りましょう' commit: '2275632' --- -今までボトムアップ (小さく始めてから複雑性を追加していく) で UI の作成に集中してきました。ボトムアップで作業することで、Storybook で遊びながら、それぞれのコンポーネントを切り離された環境で、それぞれに必要なデータを考えながら開発することができました。サーバーを立ち上げたり、画面を作ったりする必要は全くありませんでした! +今までボトムアップ (小規模な状態から複雑さを追加していく) で UI の作成に集中してきました。ボトムアップで作業することで、Storybook で遊びながら、それぞれのコンポーネントを切り離された環境で、それぞれに必要なデータを考えながら開発することができました。サーバーを立ち上げたり、画面を作ったりする必要はまったくありませんでした! この章では Storybook を使用して、コンポーネントを組み合わせて画面を作り、完成度を高めていきます。 @@ -13,7 +13,7 @@ commit: '2275632' このアプリケーションはとても単純なので、作る画面は些細なものです。リモート API からデータを取得し、(Redux から自分でデータを取得する) `TaskList` をラップして、Redux からの `error` フィールドを追加するだけです。 -まず、リモート API に接続して様々な状態 (すなわち、`error`、`succeeded`) をアプリケーションで扱えるようにするために、Redux ストア (`src/lib/store.js` 内) をアップデートするところから始めましょう: +まず、リモート API に接続してさまざまな状態 (すなわち、`error`、`succeeded`) をアプリケーションで扱えるようにするために、Redux ストア (`src/lib/store.js` 内) をアップデートするところから始めましょう。 ```diff:title=src/lib/store.js /* A simple redux store/actions/reducer implementation. @@ -32,7 +32,7 @@ import { const TaskBoxData = { tasks: [], - status: "idle", + status: 'idle', error: null, }; @@ -113,12 +113,15 @@ const store = configureStore({ export default store; ``` -リモート API エンドポイントからデータを取得するようにストアを更新し、アプリのさまざまな状態を処理できるように準備したので、`InboxScreen.js` を `src/components` ディレクトリに作成しましょう: +リモート API エンドポイントからデータを取得するようにストアを更新し、アプリのさまざまな状態を処理できるように準備したので、`InboxScreen.jsx` を `src/components` ディレクトリに作成しましょう。 -```js:title=src/components/InboxScreen.js +```jsx:title=src/components/InboxScreen.jsx import React, { useEffect } from 'react'; + import { useDispatch, useSelector } from 'react-redux'; + import { fetchTasks } from '../lib/store'; + import TaskList from './TaskList'; export default function InboxScreen() { @@ -135,8 +138,8 @@ export default function InboxScreen() {
-
Oh no!
-
Something went wrong
+

Oh no!

+

Something went wrong

); @@ -144,9 +147,7 @@ export default function InboxScreen() { return (
@@ -154,11 +155,14 @@ export default function InboxScreen() { } ``` -さらに、`App` コンポーネントを `InboxScreen` を描画するように変更します (いずれはルーターにどの画面を表示するか決めてもらいますが、今は気にしないでください): +さらに、`App` コンポーネントを `InboxScreen` を描画するように変更します (いずれはルーターにどの画面を表示するか決めてもらいますが、今は気にしないでください)。 + +```diff:title=src/App.jsx +- import { useState } from 'react' +- import reactLogo from './assets/react.svg' +- import viteLogo from '/vite.svg' +- import './App.css' -```diff:title=src/App.js -- import logo from './logo.svg'; -- import './App.css'; + import './index.css'; + import store from './lib/store'; @@ -166,22 +170,29 @@ export default function InboxScreen() { + import InboxScreen from './components/InboxScreen'; function App() { +- const [count, setCount] = useState(0) return ( -
--
-- logo +-
+- +- Vite logo +- +- +- React logo +- +-
+-

Vite + React

+-
+- -

-- Edit src/App.js and save to reload. +- Edit src/App.jsx and save to test HMR -

-- -- Learn React -- --
+-
+-

+- Click on the Vite and React logos to learn more +-

- + + @@ -193,11 +204,9 @@ export default App; しかし、面白くなるのは Storybook でストーリーをレンダリングするときです。 -前回見たように、`TaskList` コンポーネントは現在 **接続された** コンポーネントで、タスクのレンダリングは Redux ストアに依存しています。`InboxScreen` も接続されたコンポーネントなので、同じように、ストーリーにストアを渡します。以下のように `InboxScreen.stories.js` でストーリーを設定します: - -```js:title=src/components/InboxScreen.stories.js -import React from 'react'; +前回見たように、`TaskList` コンポーネントは現在 **接続された** コンポーネントで、タスクのレンダリングは Redux ストアに依存しています。`InboxScreen` も接続されたコンポーネントなので、同じように、ストーリーにストアを渡します。以下のように `InboxScreen.stories.jsx` でストーリーを設定します。 +```jsx:title=src/components/InboxScreen.stories.jsx import InboxScreen from './InboxScreen'; import store from '../lib/store'; @@ -207,61 +216,62 @@ export default { component: InboxScreen, title: 'InboxScreen', decorators: [(story) => {story()}], + tags: ['autodocs'], }; -const Template = () => ; +export const Default = {}; -export const Default = Template.bind({}); -export const Error = Template.bind({}); +export const Error = {}; ``` -私たちは `error` ストーリーですぐに問題を発見することができます。正しい状態が表示されず、タスクのリストが表示されます。この問題を回避する 1 つの方法は、前章で行ったように各状態に対してモックされたバージョンを提供することです。その代わりに、よく知られた API モッキングライブラリを Storybook アドオンと一緒に使用して、この問題を解決するのに役立てます。 +私たちは `error` ストーリーですぐに問題を発見できます。正しい状態が表示されず、タスクのリストが表示されます。この問題を回避する 1 つの方法は、前章で行ったように各状態に対してモックされたバージョンを提供することです。その代わりに、よく知られた API モッキングライブラリを Storybook アドオンと一緒に使用して、この問題を解決するのに役立てます。 -![壊れた Inbox 画面の状態](/intro-to-storybook/broken-inbox-error-state-optimized.png) +![壊れた Inbox 画面の状態](/intro-to-storybook/broken-inbox-error-state-7-0-optimized.png) ## API をモックする -今回のアプリケーションは単純で、リモート API 呼び出しにあまり依存しないので、[Mock Service Worker](https://mswjs.io/) と [Storybook's MSW addon](https://storybook.js.org/addons/msw-storybook-addon) を使用することにします。Mock Service Worker は、API モックライブラリです。Service Worker に依存してネットワークリクエストを捕捉し、モックデータをレスポンスします。 +今回のアプリケーションは単純で、リモート API 呼び出しにあまり依存しないので、[Mock Service Worker](https://mswjs.io/) と [Storybook MSW アドオン](https://storybook.js.org/addons/msw-storybook-addon) を使用することにします。Mock Service Worker は、API モックライブラリです。Service Worker に依存してネットワークリクエストを捕捉し、モックデータをレスポンスします。 -[Get started section](/intro-to-storybook/react/en/get-started) でアプリケーションをセットアップすると、両方のパッケージともインストールされます。あとは、それらを設定しストーリーを更新して使用するのみです。 +[初めの章](/intro-to-storybook/react/ja/get-started) でアプリケーションをセットアップしたときに、これらのパッケージはすでにインストールされています。あとは、それらを設定しストーリーを更新して使用するのみです。 -ターミナルで以下のコマンドを実行し、`public` フォルダの中にサービスワーカーを生成します。: +ターミナルで以下のコマンドを実行し、`public` フォルダの中にサービスワーカーを生成します。 ```shell yarn init-msw ``` -その後、`.storybook/preview.js` をアップデートしてそれらを初期化する必要があります: +その後、`.storybook/preview.js` をアップデートして初期化してください。 ```diff:title=.storybook/preview.js import '../src/index.css'; + // Registers the msw addon -+ import { initialize, mswDecorator } from 'msw-storybook-addon'; ++ import { initialize, mswLoader } from 'msw-storybook-addon'; + // Initialize MSW + initialize(); -+ // Provide the MSW addon decorator globally -+ export const decorators = [mswDecorator]; - //👇 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/react').Preview } */ +const preview = { + parameters: { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, }, }, ++ loaders: [mswLoader], }; -``` -最後に、`InboxScreen` のストーリーを更新し、リモート API 呼び出しをモックする [parameter](https://storybook.js.org/docs/react/writing-stories/parameters) を組み込みます: +export default preview; +``` -```diff:title=src/components/InboxScreen.stories.js -import React from 'react'; +最後に、`InboxScreen` のストーリーを更新し、リモート API 呼び出しをモックする [parameter](https://storybook.js.org/docs/react/writing-stories/parameters) を組み込みます。 +```diff:title=src/components/InboxScreen.stories.jsx import InboxScreen from './InboxScreen'; import store from '../lib/store'; + import { rest } from 'msw'; @@ -272,12 +282,11 @@ export default { component: InboxScreen, title: 'InboxScreen', decorators: [(story) => {story()}], + tags: ['autodocs'], }; -const Template = () => ; - -export const Default = Template.bind({}); -+ Default.parameters = { +export const Default = { ++ parameters: { + msw: { + handlers: [ + rest.get( @@ -288,10 +297,10 @@ export const Default = Template.bind({}); + ), + ], + }, -+ }; - -export const Error = Template.bind({}); -+ Error.parameters = { ++ }, +}; +export const Error = { ++ parameters: { + msw: { + handlers: [ + rest.get( @@ -302,7 +311,8 @@ export const Error = Template.bind({}); + ), + ], + }, -+ }; ++ }, +}; ```
@@ -314,7 +324,7 @@ Storybook で `error` ストーリーが意図したように動作している @@ -331,13 +341,11 @@ Storybook の [`play`](https://storybook.js.org/docs/react/writing-stories/play- play 関数はタスクが更新されたときに UI に何が起こるかを検証するのに役立ちます。フレームワークに依存しない DOM API を使用しています。つまり、 play 関数を使って UI を操作し、人間の行動をシミュレートするストーリーを、フロントエンドのフレームワークに関係なく書くことができるのです。 -`@storybook/addon-interactions`は、一つ一つのステップごとに Storybook のテストを可視化するのに役立ちます。さらに、各インタラクションの一時停止、再開、巻き戻し、ステップ実行といった便利な UI の制御機能が備わっています。 +`@storybook/addon-interactions`は、ひとつひとつのステップごとに Storybook のテストを可視化するのに役立ちます。さらに、各インタラクションの一時停止、再開、巻き戻し、ステップ実行といった便利な UI の制御機能が備わっています。 -実際に動かしてみましょう!以下のようにして新しく作成された `InboxScreen` ストーリーを更新し、コンポーネント操作を追加してみましょう: - -```diff:title=src/components/InboxScreen.stories.js -import React from 'react'; +実際に動かしてみましょう!以下のようにして新しく作成された `InboxScreen` ストーリーを更新し、コンポーネント操作を追加してみましょう。 +```diff:title=src/components/InboxScreen.stories.jsx import InboxScreen from './InboxScreen'; import store from '../lib/store'; @@ -347,34 +355,32 @@ import { Provider } from 'react-redux'; + import { + fireEvent, -+ within, + waitFor, ++ within, + waitForElementToBeRemoved -+ } from '@storybook/testing-library'; ++ } from '@storybook/test'; export default { component: InboxScreen, title: 'InboxScreen', decorators: [(story) => {story()}], + tags: ['autodocs'], }; -const Template = () => ; - -export const Default = Template.bind({}); -Default.parameters = { - msw: { - handlers: [ - rest.get( - 'https://jsonplaceholder.typicode.com/todos?userId=1', - (req, res, ctx) => { - return res(ctx.json(MockedState.tasks)); - } - ), - ], +export const Default = { + parameters: { + msw: { + handlers: [ + rest.get( + 'https://jsonplaceholder.typicode.com/todos?userId=1', + (req, res, ctx) => { + return res(ctx.json(MockedState.tasks)); + } + ), + ], + }, }, -}; - -+ Default.play = async ({ canvasElement }) => { ++ play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + // Waits for the component to transition from the loading state + await waitForElementToBeRemoved(await canvas.findByTestId('loading')); @@ -385,35 +391,57 @@ Default.parameters = { + // Simulates pinning the third task + await fireEvent.click(canvas.getByLabelText('pinTask-3')); + }); -+ }; ++ }, +}; +export const Error = { + parameters: { + msw: { + handlers: [ + rest.get( + 'https://jsonplaceholder.typicode.com/todos?userId=1', + (req, res, ctx) => { + return res(ctx.status(403)); + } + ), + ], + }, + }, +}; ``` -新しく作成したストーリーを確認します。`Interactions` パネルをクリックすると、ストーリーの play 関数内のインタラクションのリストが表示されます。 +
+ +💡 The `@storybook/test` パッケージは `@storybook/jest`と`@storybook/testing-library`を置き換えるものです。 +より小さなバンドルサイズと、Vitest パッケージをベースにしたよりわかりやすい API を提供します。 + +
+ +`Default`ストーリーを確認します。`Interactions` パネルをクリックすると、ストーリーの play 関数内のインタラクションのリストが表示されます。 -### テスト自動化 +### test runner によるテストの自動化 -play 関数を利用して、UI を操作し、タスクを更新した場合の反応を素早く確認することができます。これによって、余計な手間をかけずに UI の一貫性を保つことができます。 +play 関数を利用して、UI を操作し、タスクを更新した場合の反応を素早く確認できます。これによって、余計な手間をかけずに UI の一貫性を保つことができます。 -しかし、Storybook をよく見ると、ストーリーを見るときだけインタラクションテストが実行されることがわかります。そのため、変更時に各ストーリーを全てチェックしなければなりません。これは自動化できないのでしょうか? +しかし、Storybook をよく見ると、ストーリーを見るときだけインタラクションテストが実行されることがわかります。そのため、変更時に各ストーリーをすべてチェックしなければなりません。これは自動化できないのでしょうか? -可能です!Storybook の[テストランナー](https://storybook.js.org/docs/react/writing-tests/test-runner)は可能にしてくれます。それは [Playwright](https://playwright.dev/) によって実現されたスタンドアロンなパッケージで、全てのインタラクションテストを実行し、壊れたストーリーを検知してくれます。 +結論から言うと、それは可能です!Storybook の[テストランナー](https://storybook.js.org/docs/react/writing-tests/test-runner)は [Playwright](https://playwright.dev/) によって実現されたスタンドアロンなパッケージで、すべのインタラクションテストを実行し、壊れたストーリーを検知してくれます。 -それではどのように動くのかみてみましょう!次のコマンドでインストールして走らせます: +それではどのように動くのかみてみましょう!次のコマンドでインストールします。 ```bash yarn add --dev @storybook/test-runner ``` -次に、 `package.json` の `scripts` をアップデートし、新しいテストタスクを追加してください: +次に、 `package.json` の `scripts` を更新し、新しいテストタスクを追加してください。 -```json +```json:clipboard=false { "scripts": { "test-storybook": "test-storybook" @@ -421,7 +449,7 @@ yarn add --dev @storybook/test-runner } ``` -最後に、Storybook を起動し、新しいターミナルで以下のコマンドを実行してください: +最後に、Storybook を起動し、新しいターミナルで以下のコマンドを実行してください。 ```bash yarn test-storybook --watch @@ -433,13 +461,13 @@ yarn test-storybook --watch テストをさらにもっと深く知るためには、Testing Handbook をチェックしてみてください。これは開発ワークフローを加速させるために、スケーラブルなフロントエンドチームが採用しているテスト戦略について解説しています。
-![Storybook test runner successfully runs all tests](/intro-to-storybook/storybook-test-runner-execution.png) +![Storybook のテストランナーがすべてのテストの実行を成功させる様子](/intro-to-storybook/storybook-test-runner-execution.png) -成功です!これで、全てのストーリーがエラーなくレンダリングされ、全てのテストが自動的に通過するかどうか検証するためのツールができました。さらに、テストが失敗した場合、失敗したストーリーをブラウザで開くリンクを提供してくれます。 +成功です!これで、すべてのストーリーがエラーなくレンダリングされ、すべてのテストが自動的に通過するかどうか検証するためのツールができました。さらに、テストが失敗した場合、失敗したストーリーをブラウザで開くリンクを提供してくれます。 ## コンポーネント駆動開発 -まず、一番下の `Task` から始めて、`TaskList` を作り、画面全体の UI が出来ました。`InboxScreen` では繋がれたコンポーネントを含み、一緒にストーリーも作成しました。 +まず、一番下の `Task` から始めて、`TaskList` を作り、画面全体の UI ができました。`InboxScreen` では繋がれたコンポーネントを含み、一緒にストーリーも作成しました。 -[**コンポーネント駆動開発**](https://www.componentdriven.org/) (CDD) はコンポーネント階層を上がるごとに少しずつ複雑性を拡張していきます。利点としては、開発プロセスに集中できること、UI の組み合わせの網羅性を向上できること、が挙げられます。要するに、CDD によって、高品質で複雑な UI を作ることができます。 +[**コンポーネント駆動開発**](https://www.componentdriven.org/) (CDD) はコンポーネント階層を上がるごとに少しずつ複雑性を拡張します。利点としては、開発プロセスに集中できること、UI の組み合わせの網羅性を向上できること、が挙げられます。要するに、CDD によって、高品質で複雑な UI を作ることができます。 -まだ終わりではありません。UI を作成しても仕事は終わりません。長期間にわたり耐久性を維持できるようにしなければなりません。 +まだ終わりではありません。UI を作成しても作業は終わりません。長期間にわたり耐久性を維持できるようにしなければなりません。
💡 Git へのコミットを忘れずに行ってください! diff --git a/content/intro-to-storybook/react/ja/simple-component.md b/content/intro-to-storybook/react/ja/simple-component.md index 45888586e..3dad1b504 100644 --- a/content/intro-to-storybook/react/ja/simple-component.md +++ b/content/intro-to-storybook/react/ja/simple-component.md @@ -5,7 +5,7 @@ description: '単純なコンポーネントを切り離して作りましょう commit: '9b36e1a' --- -それでは、[コンポーネント駆動開発](https://www.componentdriven.org/) (CDD) の手法にのっとって UI を作ってみましょう。コンポーネント駆動開発とは、UI を最初にコンポーネントから作り始めて、最後に画面を作り上げる「ボトムアップ」の開発プロセスです。CDD を用いれば UI を作る際に直面する複雑性を軽減することができます。 +それでは、[コンポーネント駆動開発](https://www.componentdriven.org/) (CDD) の手法にのっとって UI を作ってみましょう。コンポーネント駆動開発とは、UI を最初にコンポーネントから作り始めて、最後に画面を作り上げる「ボトムアップ」の開発プロセスです。CDD を用いれば、 UI を作る際に直面する複雑性を軽減できます。 ## Task (タスク) @@ -20,17 +20,19 @@ commit: '9b36e1a' ## セットアップする -まずは、タスクのコンポーネントと、対応するストーリーファイル `src/components/Task.js` と `src/components/Task.stories.js` を作成しましょう。 +まずは、タスクのコンポーネントと、対応するストーリーファイル `src/components/Task.jsx` と `src/components/Task.stories.jsx` を作成しましょう。 -`Task` の基本的な実装から始めます。`Task` は上述したプロパティと、タスクに対して実行できる 2 つの (リスト間を移動させる) アクションを引数として取ります: +`Task` の基本的な実装から始めます。`Task` は上述したプロパティと、タスクに対して実行できる 2 つの (リスト間を移動させる) アクションを引数として取ります。 -```js:title=src/components/Task.js +```js:title=src/components/Task.jsx import React from 'react'; export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { return (
- +
); } @@ -38,48 +40,47 @@ export default function Task({ task: { id, title, state }, onArchiveTask, onPinT 上のコードは Todo アプリケーションの HTML を基にした `Task` の簡単なマークアップです。 -下のコードは `Task` に対する 3 つのテスト用の状態をストーリーファイルに書いています: - -```js:title=src/components/Task.stories.js -import React from 'react'; +下のコードは `Task` に対する 3 つのテスト用の状態をストーリーファイルに書くものです。 +```js:title=src/components/Task.stories.jsx import Task from './Task'; export default { component: Task, title: 'Task', + tags: ['autodocs'], }; -const Template = args => ; - -export const Default = Template.bind({}); -Default.args = { - task: { - id: '1', - title: 'Test Task', - state: 'TASK_INBOX', - updatedAt: new Date(2021, 0, 1, 9, 0), +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', + }, }, }; ``` -Storybook には基本となる 2 つの階層があります。コンポーネントとその子供となるストーリーです。各ストーリーはコンポーネントに連なるものだと考えてください。コンポーネントには必要なだけストーリーを記述することができます。 +Storybook には基本となる 2 つの階層があります。コンポーネントとその子供となるストーリーです。各ストーリーはコンポーネントに連なるものだと考えてください。コンポーネントには必要なだけストーリーを記述できます。 - **コンポーネント** - ストーリー @@ -89,72 +90,66 @@ Storybook には基本となる 2 つの階層があります。コンポーネ Storybook にコンポーネントを認識させるには、以下の内容を含む `default export` を記述します: - `component` -- コンポーネント自体 -- `title` -- Storybook のサイドバーにあるコンポーネントを参照する方法 - -ストーリーを定義するには、テスト用の状態ごとにストーリーを生成する関数をエクスポートします。ストーリーとは、特定の状態で描画された要素 (例えば、プロパティを指定したコンポーネントなど) を返す関数で、React の[状態を持たない関数コンポーネント](https://reactjs.org/docs/components-and-props.html#function-and-class-components)のようなものです。 +- `title` -- Storybookのサイドバーでコンポーネントをグループ化または分類するためのタイトル +- `tags` -- このコンポーネントのドキュメントを自動生成するためのタグ -コンポーネントにストーリーが複数連なっているので、各ストーリーを単一の `Template` 変数に割り当てるのが便利です。このパターンを導入することで、書くべきコードの量が減り、保守性も上がります。 - -
-💡 Template.bind({}) は関数のコピーを作成する JavaScript の標準的な テクニックで、同じ実装を使いながら、エクスポートされたそれぞれのストーリーに独自のプロパティを設定することができます。 -
+ストーリーを定義するには、コンポーネント ストーリー フォーマット 3 ( [CSF3](https://storybook.js.org/docs/react/api/csf) )を使用してテストケースを構築します。このフォーマットは、各テストケースを簡潔に構築するために設計されています。各コンポーネントの状態を含むオブジェクトをエクスポートすることで、テストをより直感的に定義し、ストーリーをより効率的に作成・再利用できます。 Arguments (略して [`args`](https://storybook.js.org/docs/react/writing-stories/args)) を使用することで、コントロールアドオンを通して、Storybook を再起動することなく、コンポーネントを動的に編集することができるようになります。[`args`](https://storybook.js.org/docs/react/writing-stories/args) の値が変わるとコンポーネントもそれに合わせて変わります。 ストーリーを作る際には素となるタスク引数を使用してコンポーネントが想定するタスクの状態を作成します。想定されるデータは実際のデータと同じように作ります。さらに、このデータをエクスポートすることで、今後作成するストーリーで再利用することが可能となります。 -
-💡 アクションアドオンは切り離された環境で UI コンポーネントを開発する際の動作確認に役立ちます。アプリケーションの実行中には状態や関数を参照出来ないことがよくあります。action() はそのスタブとして使用できます。 -
- ## 設定する -作成したストーリーを認識させ、[前の章](/intro-to-storybook/react/ja/get-started)で変更した CSS ファイルを使用できるようにするため、Storybook の設定をいくつか変更する必要があります。 +作成したストーリーを認識させたり、CSS ファイル (`src/index.css`にあります) をStorybook上で使用できるようにするため、Storybook の設定をいくつか変更する必要があります。 -まず、設定ファイル (`.storybook/main.js`) を以下のように変更してください: +まず、設定ファイル (`.storybook/main.js`) を以下のように変更してください。 ```diff:title=.storybook/main.js -module.exports = { -- stories: [ -- '../src/**/*.stories.mdx', -- '../src/**/*.stories.@(js|jsx|ts|tsx)' -- ], -+ stories: ['../src/components/**/*.stories.js'], +/** @type { import('@storybook/react-vite').StorybookConfig } */ +const config = { +- stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], ++ stories: ['../src/components/**/*.stories.@(js|jsx)'], staticDirs: ['../public'], addons: [ '@storybook/addon-links', '@storybook/addon-essentials', - '@storybook/preset-create-react-app', '@storybook/addon-interactions', ], - features: { - postcss: false, + framework: { + name: '@storybook/react-vite', + options: {}, }, - framework: '@storybook/react', - core: { - builder: 'webpack4', + docs: { + autodocs: 'tag', }, }; +export default config; ``` -上記の変更が完了したら、`.storybook` フォルダー内の `preview.js` を、以下のように変更してください: +上記の変更が完了したら、`.storybook` フォルダー内の `preview.js` を、以下のように変更してください。 ```diff:title=.storybook/preview.js + 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/react').Preview } */ +const preview = { + parameters: { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, }, }, }; + +export default preview; ``` -[`parameters`](https://storybook.js.org/docs/react/writing-stories/parameters) は Storybook の機能やアドオンの振る舞いをコントロールするのに使用します。この例では、アクション (呼び出しのモック) がどのように扱われるかを設定しています。 +[`parameters`](https://storybook.js.org/docs/react/writing-stories/parameters) は Storybook の機能やアドオンの振る舞いをコントロールするのに使用します。この例では、アクション (`actions`、呼び出しのモック) がどのように扱われるかを設定しています。 アクションアドオンを使用することで、クリックした時などに Storybook の **actions** パネルにその情報を表示するコールバックを作成できます。これにより、ピン留めボタンを作成するとき、ボタンがクリックされたことがテスト用の UI 上で確認できます。 @@ -162,7 +157,7 @@ Storybook のサーバーを再起動すると、タスクの 3 つの状態の @@ -171,9 +166,9 @@ Storybook のサーバーを再起動すると、タスクの 3 つの状態の ここまでで、Storybook のセットアップが完了し、スタイルをインポートし、テストケースを作りました。早速、デザインに合わせてコンポーネントの HTML を実装していきましょう。 -今のところコンポーネントは簡素な状態です。まずはデザインを実現するために最低限必要なコードを書いてみましょう: +今のところコンポーネントは簡素な状態です。まずはデザインを実現するために最低限必要なコードを書いてみましょう。 -```js:title=src/components/Task.js +```jsx:title=src/components/Task.jsx import React from 'react'; export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { @@ -223,11 +218,11 @@ export default function Task({ task: { id, title, state }, onArchiveTask, onPinT } ``` -追加したマークアップとインポートした CSS により以下のような UI ができます: +追加したマークアップとインポートした CSS により以下のような UI ができます。 @@ -236,12 +231,54 @@ export default function Task({ task: { id, title, state }, onArchiveTask, onPinT `propTypes` を使い React にコンポーネントが想定するデータ構造を示すのがベストプラクティスです。これにより想定するデータ構造がコードからわかるだけでなく、早期に問題を見つけるのに役立ちます。 -```diff:title=src/components/Task.js +```diff:title=src/components/Task.jsx import React from 'react'; + import PropTypes from 'prop-types'; export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { - // ... + return ( +
+ + + + + {state !== "TASK_ARCHIVED" && ( + + )} +
+ ); } + Task.propTypes = { @@ -269,48 +306,49 @@ export default function Task({ task: { id, title, state }, onArchiveTask, onPinT ## 完成! -これでサーバーを起動したり、フロントエンドアプリケーションを起動したりすることなく、コンポーネントを作りあげることができました。次の章では、Taskbox の残りのコンポーネントを、同じように少しずつ作成していきます。 +これでサーバーを起動したり、フロントエンドアプリケーションを起動したりすることなく、コンポーネントを作りあげることができました。次の章では、Taskbox の残りのコンポーネントを、同じように少しずつ作成します。 -見た通り、コンポーネントだけを切り離して作り始めるのは早くて簡単です。あらゆる状態を掘り下げてテストできるので、高品質で、バグが少なく、洗練された UI を作ることができることでしょう。 +見た通り、コンポーネントを切り離して開発を始めるのは、迅速かつ簡単です。あらゆる状態を掘り下げてテストできるので、高品質で、バグが少なく、洗練された UI を作ることができることでしょう。 ## アクセシビリティの問題の検知 -アクセシビリティテストとは、[WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/) のルールと他の業界で認めれたベストプラクティスに基づく経験則に対して、自動化ツールを用いることでレンダリングされた DOM を監視することを指します。これは視覚障害、聴覚障害、認知障害などの障害をお持ちの方を含む、できるだけ多くのユーザーがアプリケーションを利用できるように、明らかなアクセシビリティの違反を検知するために QA の第一線として機能します。 +アクセシビリティテストとは、[WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/) のルールと他の業界で認められたベストプラクティスに基づく経験則に対して、自動化ツールを用いることでレンダリングされた DOM を監視することを指します。これは視覚障害、聴覚障害、認知障害などの障害をお持ちの方を含む、できるだけ多くのユーザーがアプリケーションを利用できるように、明らかなアクセシビリティの違反を検知するために QA の第一線として機能します。 -Storybook には公式の[アクセシビリティアドオン](https://storybook.js.org/addons/@storybook/addon-a11y)があります。これは、Deque の [axe-core](https://github.com/dequelabs/axe-core) を使っており、[WCAG の問題の 57%](https://www.deque.com/blog/automated-testing-study-identifies-57-percent-of-digital-accessibility-issues/) に対応しています。 +Storybook には公式の[アクセシビリティアドオン](https://storybook.js.org/addons/@storybook/addon-a11y)があります。このアドオンは、Deque の [axe-core](https://github.com/dequelabs/axe-core) がベースで、[WCAG の問題の 57%](https://www.deque.com/blog/automated-testing-study-identifies-57-percent-of-digital-accessibility-issues/) に対応しています。 -それでは、どのように動かすのかみてみましょう! 以下のコマンドでアドオンをインストールします: +それでは、どのように動かすのか見てみましょう! 以下のコマンドでアドオンをインストールします。 ```shell yarn add --dev @storybook/addon-a11y ``` -アドオンを利用可能にするために、Storybook の設定ファイル(`.storybook/main.js`)を以下のように設定します: +アドオンを利用可能にするために、Storybook の設定ファイル(`.storybook/main.js`)を以下のように設定します。 ```diff:title=.storybook/main.js -module.exports = { - stories: ['../src/components/**/*.stories.js'], +/** @type { import('@storybook/react-vite').StorybookConfig } */ +const config = { + stories: ['../src/components/**/*.stories.@(js|jsx)'], staticDirs: ['../public'], addons: [ '@storybook/addon-links', '@storybook/addon-essentials', - '@storybook/preset-create-react-app', '@storybook/addon-interactions', + '@storybook/addon-a11y', ], - framework: '@storybook/react', - core: { - builder: '@storybook/builder-webpack5', + framework: { + name: '@storybook/react-vite', + options: {}, }, - features: { - interactionsDebugger: true, + docs: { + autodocs: 'tag', }, }; +export default config; ``` -![Task accessibility issue in Storybook](/intro-to-storybook/finished-task-states-accessibility-issue.png) +![タスクのアクセシビリティの問題をStorybookで表示する](/intro-to-storybook/finished-task-states-accessibility-issue-7-0.png) -ストーリーを一通り見てみると、このアドオンが一つのアクセシビリティの問題を検知したことがわかります。[**「Elements must have sufficient color contrast」**](https://dequeuniversity.com/rules/axe/4.4/color-contrast?application=axeAPI)というエラーメッセージは本質的にタイトルと背景に十分なコントラストがないことを指しています。そのため、アプリケーションの CSS (`src/index.css` にある)の中で、テキストカラーをより暗いグレーに修正する必要があります。 +ストーリーをひととおり見てみると、このアドオンがひとつのアクセシビリティの問題を検知したことがわかります。[**"Elements must have sufficient color contrast"**](https://dequeuniversity.com/rules/axe/4.4/color-contrast?application=axeAPI) というエラーメッセージは、タイトルと背景に十分なコントラストがないことを指しています。そのため、アプリケーションの CSS (`src/index.css`)を編集して、テキストのカラーをより暗いグレーに修正する必要があります。 ```diff:title=src/index.css .list-item.TASK_ARCHIVED input[type="text"] { @@ -320,7 +358,7 @@ module.exports = { } ``` -以上です!これで、UI のアクセシビリティ向上の最初のステップが完了です。アプリケーションをさらに複雑にしても、他の全てのコンポーネントに対してこのプロセスを繰り返すことができ、追加のツールやテスト環境を準備する必要はありません。 +以上です!これで、UI のアクセシビリティ向上の最初のステップが完了です。アプリケーションをさらに複雑にしても、他のすべてのコンポーネントに対してこのプロセスを繰り返すことができ、追加のツールやテスト環境を準備する必要はありません。
💡 Git へのコミットを忘れずに行ってください! diff --git a/content/intro-to-storybook/react/ja/test.md b/content/intro-to-storybook/react/ja/test.md index 3bf72f369..5204078d1 100644 --- a/content/intro-to-storybook/react/ja/test.md +++ b/content/intro-to-storybook/react/ja/test.md @@ -4,7 +4,7 @@ tocTitle: 'テスト' description: 'UI コンポーネントのテスト手法について学びましょう' --- -Storybook のチュートリアルをテスト抜きには終われません。テストは高品質な UI を作成するのに必要なことです。疎結合なシステムにおいては、些細な変更で大きなリグレッション (手戻り) をもたらすことがあるのです。既に 3 種類のテストについて学びました: +Storybook のチュートリアルをテスト抜きには終われません。テストは高品質な UI を作成するのに必要なことです。疎結合なシステムにおいては、些細な変更で大きなリグレッション (手戻り) をもたらすことがあるのです。このチュートリアルで、すでに 3 種類のテストについて学びました。 - **手動テスト**では、コンポーネントの正しさを開発者が手動で確認します。コンポーネントを作成する際、見た目が問題ないかチェックするのに役立ちます。 - **アクセシビリティテスト**では、a11y アドオンを使用し、コンポーネントがみなさんに受け入れやすいか検証します。これらは、どのように障害を持つ人々が私たちのコンポーネントを使うのかという情報を取得するのに大いに役立ちます。 @@ -27,23 +27,25 @@ Storybook のチュートリアルをテスト抜きには終われません。 Storybook は視覚的なリグレッションテスト用の素晴らしいツールです。Storybook において、すべてのストーリーはテスト仕様となるからです。ストーリーを書くたび、仕様が無料でついてきます! -視覚的なリグレッションテスト向けのツールは多々あります。Storybook のメンテナーが作成した無料のホスティングサービスである [**Chromatic**](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook) がおすすめです。Chromatic はクラウド上でビジュアルテストを並列実行します。[前の章](/intro-to-storybook/react/ja/deploy/)と同じように Storybook をインターネット上に発行出来ます。 +視覚的なリグレッションテスト向けのツールは多々あります。Storybook のメンテナーが作成した無料のホスティングサービスである [**Chromatic**](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook) がオススメです。Chromatic はクラウド環境上でビジュアルテストを光の速さで並列実行します。[前の章](/intro-to-storybook/react/ja/deploy/)で見てきたように、Storybook をインターネット上に公開することもできます。 ## UI の変更を検知する -視覚的なリグレッションテストでは UI コードにより描画されたイメージが基準となるイメージと比較されます。ツールが UI の変更が検知したら、教えてくれます。 +視覚的なリグレッションテストでは UI コードにより描画されたイメージが基準となるイメージと比較されます。ツールが UI の変更が検知したら、通知を受け取ることができます。 それでは、`Task` コンポーネントの背景を変更し、どう動くのか見てみましょう。 -変更する前に新しいブランチを作成します: +変更する前に新しいブランチを作成します。 ```shell git checkout -b change-task-background ``` -`src/components/Task.js` を以下のように変更します: +`src/components/Task.jsx` を以下のように変更します。 + +```diff:title=src/components/Task.jsx +import React from 'react'; -```diff:title=src/components/Task.js export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { return (
@@ -94,35 +96,35 @@ export default function Task({ task: { id, title, state }, onArchiveTask, onPinT これでタスクの背景色が変更されます。 -![task background change](/intro-to-storybook/chromatic-task-change.png) +![タスクの背景色の変更](/intro-to-storybook/chromatic-task-change.png) -この変更をステージングします: +この変更をステージングします。 ```shell git add src/components/Task.js ``` -コミットします: +コミットします。 ```shell git commit -m “change task background to red” ``` -そして変更をリモートリポジトリーにプッシュします: +そして変更をリモートリポジトリにプッシュします。 ```shell git push -u origin change-task-background ``` -最後に、GitHub のリポジトリーを開き `change-task-background` ブランチのプルリクエストを作成します。 +最後に、ブラウザでGitHub のリポジトリを開き `change-task-background` ブランチのプルリクエストを作成します。 ![GitHub にタスクの PR を作成する](/github/pull-request-background.png) -プルリクエストに適切な説明を書き、`Create pull request` をクリックしてください。ページの下部に表示された「🟡 UI Tests」の PR チェックをクリックして下さい。 +プルリクエストに適切な説明を書き、`Create pull request` をクリックしてください。その後、ページの下部に表示された「🟡 UI Tests」の PR チェックをクリックしてください。 ![GitHub にタスクの PR が作成された](/github/pull-request-background-ok.png) -これで先のコミットによって検出された UI の変更が見られます。 +これで先のコミットによって検出された UI の変更を見られます。 ![Chromatic が変更を検知した](/intro-to-storybook/chromatic-catch-changes.png) @@ -151,6 +153,6 @@ UI の変更をレビューしたら、その変更で意図せずバグを混 ![マージの準備ができた変更内容](/intro-to-storybook/chromatic-review-finished.png) -Storybook はコンポーネントを**作る**のに役立ち、テストはコンポーネントを**保つ**のに役立ちます。手動テスト、スナップショットテスト、単体テスト、ビジュアルテストの 4 種類をこのチュートリアルで学びました。最後の 3 種類は、今設定したように、CI に追加することで自動化することができます。これによりコンポーネントのバグに気づかないことを心配をせずにリリースできます。このワークフロー全体は以下の図の通りです。 +Storybook はコンポーネントを**作る**のに役立ち、テストはコンポーネントを**保つ**のに役立ちます。手動テスト、スナップショットテスト、単体テスト、ビジュアルテストの 4 種類をこのチュートリアルで学びました。最後の 3 種類は、今設定したように、CI に追加することで自動化できます。これによりコンポーネントのバグに気づかないことを心配をせずにリリースできます。このワークフロー全体は以下の図の通りです。 ![視覚的なリグレッションテストのワークフロー](/intro-to-storybook/cdd-review-workflow.png) diff --git a/content/intro-to-storybook/react/ja/using-addons.md b/content/intro-to-storybook/react/ja/using-addons.md index 07875ba35..7ba47ceae 100644 --- a/content/intro-to-storybook/react/ja/using-addons.md +++ b/content/intro-to-storybook/react/ja/using-addons.md @@ -9,17 +9,17 @@ Storybook にはチームの開発効率を向上する堅牢な[アドオン](h ここまでチュートリアルを進めてきたのであれば、すでにいくつかのアドオンに遭遇し、[テストの章](/intro-to-storybook/react/ja/test/)では導入もしています。 -多様なユースケースに対応するためのアドオンがあるので、それを全て説明することは出来ません。ここでは最も人気のあるアドオンである [コントロールアドオン](https://storybook.js.org/docs/react/essentials/controls) を導入してみましょう。 +多様なユースケースに対応するためのアドオンがあるので、それをすべて説明することはできません。ここではもっとも人気のあるアドオンである [コントロールアドオン](https://storybook.js.org/docs/react/essentials/controls) を導入してみましょう。 ## コントロールアドオンとは コントロールアドオンはデザイナーや、開発者がコードを書かずにパラメータを*いじって遊びながら*コンポーネントの挙動を探求できるようにするアドオンです。このアドオンはストーリーの隣にパネルを表示し、そこから直にコンポーネントの引数を編集できます。 -Storybook を新たにインストールすると、はじめから Contorls が使用可能です。追加で設定する必要はありません。 +Storybook を新たにインストールすると、はじめから Contorls が使用可能です。追加の設定は必要ありません。 @@ -30,61 +30,103 @@ Storybook はすばらしい[コンポーネント駆動開発](https://www.comp ## コントロールアドオンをエッジケースを見つけるのに使用する -コントロールアドオンを使用すれば、品質管理者や、UI エンジニアや、その他のステークホルダーがコンポーネントを限界まで操作できます!例えば `Task` コンポーネントに*大量の*文字列を渡したらどうなるでしょうか。 +コントロールアドオンを使用すれば、品質管理者や、UI エンジニアや、その他のステークホルダーがコンポーネントを限界まで操作できます!たとえば `Task` コンポーネントに*大量の*文字列を渡したらどうなるでしょうか。 -![しまった!右側の文字列が切れている!](/intro-to-storybook/task-edge-case-6-4.png) +![しまった!右側の文字列が切れている!](/intro-to-storybook/task-edge-case-7-0.png) これはマズいです。タスクコンポーネントの境界を越えて文字列があふれています。 コントロールアドオンを使用すればコンポーネントにいろいろな入力をして素早く確認できます。今回は長い文字列を入力しました。これで UI の問題を発見する際の作業を減らすことができます。 -それでは `Task.js` にスタイルを追加して、この文字切れ問題を解決しましょう: - -```diff:title=src/components/Task.js - +それでは `Task.jsx` にスタイルを追加して、この文字切れ問題を解決しましょう。 + +```diff:title=src/components/Task.jsx +import React from 'react'; + +export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { + return ( +
+ + + + + {state !== "TASK_ARCHIVED" && ( + + )} +
+ ); +} ``` -![良くなりました](/intro-to-storybook/edge-case-solved-with-controls-6-4.png) +![良くなりました](/intro-to-storybook/edge-case-solved-with-controls-7-0.png) -問題は解決しました!文字列がタスクの境界に達したらかっこいい省略記号で切り詰められるようになりました。 +問題が解決しました!文字列が境界に達すると、かっこいい省略記号で切り詰められるようになりました。 ## リグレッションを回避するためストーリーを追加する -今後もコントロールアドオンを使用して、手動で同じ入力をすればいつでもこの問題は再現可能です。ですが、このエッジケースに対応するストーリーを書く方が簡単です。ストーリーを書くことにより、リグレッションテストのカバレッジが向上しますし、コンポーネントの限界をチームメンバーに明示することが出来ます。 +今後もコントロールアドオンを使用して、手動で同じ入力をすればいつでもこの問題は再現可能です。ですが、このエッジケースに対応するストーリーを書く方が簡単です。ストーリーを書くことにより、リグレッションテストのカバレッジが向上しますし、コンポーネントの限界をチームメンバーに明示できます。 -それでは `Task.stories.js` ファイルに長い文字列が指定された場合のストーリーを追加しましょう: +それでは `Task.stories.jsx` ファイルに長い文字列が指定された場合のストーリーを追加しましょう。 -```js:title=src/components/Task.stories.js +```js:title=src/components/Task.stories.jsx const longTitleString = `This task's name is absurdly large. In fact, I think if I keep going I might end up with content overflow. What will happen? The star that represents a pinned task could have text overlapping. The text could cut-off abruptly when it reaches the star. I hope not!`; -export const LongTitle = Template.bind({}); -LongTitle.args = { - task: { - ...Default.args.task, - title: longTitleString, +export const LongTitle = { + args: { + task: { + ...Default.args.task, + title: longTitleString, + }, }, }; ``` -これで、このエッジケースをいつでも再現できるようになりました: +これで、このエッジケースをいつでも再現できるようになりました。 -[ビジュアルテスト](/intro-to-storybook/react/ja/test/)を使用している場合は、文字の省略が壊れた場合に分かるようになります。このように曖昧なエッジケースはテストなしには忘れてしまいがちです! - -

💡 開発者ではない人でも、コントロールアドオンを使うことでコンポーネントやストーリーを触れるようになります。さらに理解を深めるためには公式のドキュメントを見てください。アドオンを使用して Storybook をカスタマイズする方法は 1 つではありません。おまけの章である、アドオンを作るではアドオンを使用して開発を加速する方法を説明します。

+[ビジュアルテスト](/intro-to-storybook/react/ja/test/)を使用している場合は、文字の省略が壊れた場合にわかるようになります。このように曖昧なエッジケースはテストなしには忘れてしまいがちです! -### 変更をマージする +

💡 開発者でない人でも、コントロールアドオンを使うことでコンポーネントやストーリーを触れるようになります。さらに理解を深めるためには公式ドキュメントを参照してください。アドオンを使用して Storybook をカスタマイズする方法は 1 つではありません。おまけの章である、アドオンを作るではアドオンを使用して開発を加速する方法を説明します。

-変更を忘れずに Git にマージしてください! +
+💡 Git へのコミットを忘れずに行ってください! +