diff --git a/content/intro-to-storybook/react/fr/composite-component.md b/content/intro-to-storybook/react/fr/composite-component.md index ea4d22466..25da79f14 100644 --- a/content/intro-to-storybook/react/fr/composite-component.md +++ b/content/intro-to-storybook/react/fr/composite-component.md @@ -19,13 +19,11 @@ Comme les données de `Task` peuvent être envoyées de manière asynchrone, nou ## Se préparer -Un composant complexe n'est pas très différent des composants de base qu'il contient. Créez un composant `TaskList` et sa story associée: `src/components/TaskList.js` et `src/components/TaskList.stories.js`. +Un composant complexe n'est pas très différent des composants de base qu'il contient. Créez un composant `TaskList` et sa story associée: `src/components/TaskList.jsx` et `src/components/TaskList.stories.jsx`. Commencez par une implémentation simpliste de la `TaskList`. Vous devrez importer le composant `Task` de la version précédente, puis lui passer les attributs et les actions en entrée. -```js:title=src/components/TaskList.js -import React from 'react'; - +```jsx:title=src/components/TaskList.jsx import Task from './Task'; export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { @@ -54,61 +52,66 @@ export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { Ensuite, créez les états de test de la `Tasklist` dans la story. -```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'], + args: { + ...TaskStories.ActionsData, + }, }; -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, + }, }; ```
-Les décorateurs sont un moyen de fournir une encapsulation arbitraire aux stories. Dans notre cas, nous utilisons une clé (`key`) de décorateur dans l'export par défaut pour ajouter du `padding` autour du composant rendu. Ils peuvent également être utilisés pour encapsuler des stories dans des "providers" - c'est-à-dire des composants de bibliothèque qui définissent le contexte de React. + +💡 Les [**décorateurs**](https://storybook.js.org/docs/writing-stories/decorators) sont un moyen de fournir des enveloppes arbitraires aux stories. Dans ce cas, nous utilisons une clé de décorateur dans l'export par défaut pour ajouter une `margin` autour du composant rendu. Ils peuvent également être utilisés pour encapsuler des stories dans des "providers" - c'est-à-dire des composants de bibliothèque qui définissent le contexte React. +
En important `TaskStories`, nous avons pu [composer](https://storybook.js.org/docs/react/writing-stories/args#args-composition) les arguments (args pour faire court) de nos stories avec un minimum d'effort. De cette façon, les données et les actions (callbacks simulés) attendues par les deux composants sont préservées. @@ -117,7 +120,7 @@ Maintenant, regardez les nouvelles stories de la `TaskList` dans Storybook. @@ -126,9 +129,7 @@ Maintenant, regardez les nouvelles stories de la `TaskList` dans Storybook. Notre composant est encore rudimentaire, mais nous avons maintenant une idée des stories à travailler. Vous pensez peut-être que la présentation de la `.list-items` est trop simpliste. Vous avez raison - dans la plupart des cas, nous ne créerions pas un nouveau composant juste pour ajouter une enveloppe. Mais la **réelle complexité** du composant `TaskList` est révélée dans les cas particuliers `withPinnedTasks`, `loading`, et `empty`. -```js:title=src/components/TaskList.js -import React from 'react'; - +```jsx:title=src/components/TaskList.jsx import Task from './Task'; export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { @@ -169,8 +170,8 @@ export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { } 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 (
@@ -186,7 +187,7 @@ Les balises ajoutées donnent l'interface suivante : @@ -197,8 +198,7 @@ Notez la position de l'élément épinglé dans la liste. Nous voulons que l'él Au fur et à mesure que le composant grossit, le nombre de données à l'entrée augmente également. Définissez les exigences en matière de props de la `TaskList`. Comme `Task` est un composant enfant, assurez-vous de fournir des données de la bonne manière pour le rendu. Pour gagner du temps et épargner des soucis, réutilisez les `propTypes` que vous avez définis dans `Task` précédemment. -```diff:title=src/components/TaskList.js -import React from 'react'; +```diff:title=src/components/TaskList.jsx + import PropTypes from 'prop-types'; import Task from './Task'; @@ -241,8 +241,8 @@ export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { } 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 (
@@ -269,5 +269,5 @@ export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { ```
-💡 N'oubliez pas de commiter vos changements avec git! +💡 N'oubliez pas de commiter vos changements avec git !
diff --git a/content/intro-to-storybook/react/fr/conclusion.md b/content/intro-to-storybook/react/fr/conclusion.md index 4d93cb967..d3accdfb3 100644 --- a/content/intro-to-storybook/react/fr/conclusion.md +++ b/content/intro-to-storybook/react/fr/conclusion.md @@ -3,10 +3,11 @@ title: 'Conclusion' description: 'Combinez toutes vos connaissances et apprenez plus de techniques de Storybook' --- -Félicitations! Vous avez créé votre premiere interface utilisateur dans Storybook. En cours de route, vous avez appris à construire, assembler, tester et déployer des composants graphiques. Si vous avez suivi les étapes, votre projet et votre Storybook déployé devraient ressembler à ceci: +Félicitations ! Vous avez créé votre premiere interface utilisateur dans Storybook. En cours de route, vous avez appris à construire, assembler, tester et déployer des composants graphiques. Si vous avez suivi les étapes, votre projet et votre Storybook déployé devraient ressembler à ceci : [📕 **GitHub repo: chromaui/learnstorybook-code**](https://github.com/chromaui/learnstorybook-code)
+ [🌎 **Storybook déployé**](https://master--5ccbe484c994280020b6d128.chromatic.com) Storybook est un outil puissant pour React, React Native, Vue, Angular, Svelte et bien d'autres. Il dispose d'une communauté de développeurs florissante et d'une multitude d'addons. Cette introduction décrit brièvement une infime partie de ce qu'il est possible de faire. Nous sommes persuadés qu'une fois que vous aurez adopté Storybook, vous serez impressionné de voir comme il est fructueux de créer des UI durables. diff --git a/content/intro-to-storybook/react/fr/data.md b/content/intro-to-storybook/react/fr/data.md index 716b493c8..32d5134fc 100644 --- a/content/intro-to-storybook/react/fr/data.md +++ b/content/intro-to-storybook/react/fr/data.md @@ -83,10 +83,11 @@ export default store; Ensuite, mettons à jour le composant `TaskList` pour se connecter au store Redux et rendre les tâches qui nous intéressent: -```js:title=src/components/TaskList.js -import React from 'react'; +```jsx:title=src/components/TaskList.jsx import Task from './Task'; + import { useDispatch, useSelector } from 'react-redux'; + import { updateTaskState } from '../lib/store'; export default function TaskList() { @@ -287,18 +288,20 @@ Empty.decorators = [ ```
-💡 excludeStories est un champ de la configuration Storybook qui empêche notre état simulé d'être traité comme une story. Vous pouvez en savoir plus sur ce champ dans la documentation Storybook. + +💡 `excludeStories` est un champ de la configuration Storybook qui empêche notre état simulé d'être traité comme une story. Vous pouvez en savoir plus sur ce champ dans la [documentation Storybook](https://storybook.js.org/docs/api/csf). +
-💡 N'oubliez pas de commiter vos changements avec git! +💡 N'oubliez pas de commiter vos changements avec git !
Félicitations ! Nous sommes de nouveau opérationnel, notre Storybook fonctionne, et nous sommes capables de récupérer de la donnée à travers un composant connecté. Dans le prochain chapitre, nous utiliserons ce que nous avons appris and nous l'appliquerons à un écran. diff --git a/content/intro-to-storybook/react/fr/deploy.md b/content/intro-to-storybook/react/fr/deploy.md index 9403bf754..692d8c0b0 100644 --- a/content/intro-to-storybook/react/fr/deploy.md +++ b/content/intro-to-storybook/react/fr/deploy.md @@ -15,7 +15,7 @@ L'exécution de `yarn build-storybook` produira un Storybook statique dans le r ## Publier Storybook -Ce tutoriel utilise Chromatic, un service de publication gratuit réalisé par les mainteneurs de Storybook. Il nous permet de déployer et d'héberger notre Storybook en toute sécurité dans le cloud. +Ce tutoriel utilise [Chromatic](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook), un service de publication gratuit réalisé par les mainteneurs de Storybook. Il nous permet de déployer et d'héberger notre Storybook en toute sécurité dans le cloud. ### Configurer un dépôt dans GitHub @@ -89,15 +89,17 @@ 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/react/fr/deploy/ to obtain it @@ -105,7 +107,11 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} ``` -

💡 Pour des raisons de sécurité les secrets de GitHub n'ont pas été mentionnés. Les secrets sont des variables d'environnement sécurisées fournies par GitHub afin que vous n'ayez pas besoin de coder directement le projet-token.

+
+ +💡 Pour des raisons de sécurité les [secrets GitHub](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository) n'ont pas été mentionnés. Les secrets sont des variables d'environnement sécurisées fournies par GitHub afin que vous n'ayez pas besoin de coder directement le `project-token`. + +
### Commiter l'action @@ -135,6 +141,6 @@ Cliquez sur le dernier build, cela doit être celui du haut. Ensuite, cliquez sur le bouton `View Storybook` pour voir la dernière version de votre Storybook. -![Lien Storybook sur Chromatic](/intro-to-storybook/chromatic-build-storybook-link-6-4-optimized.png) +![Lien Storybook sur Chromatic](/intro-to-storybook/chromatic-build-storybook-link.png) Utilisez le lien et partagez-le avec les membres de votre équipe. Ceci est utile dans le cadre du processus de développement standard d'une application ou simplement pour montrer son travail 💅. diff --git a/content/intro-to-storybook/react/fr/get-started.md b/content/intro-to-storybook/react/fr/get-started.md index 0d358a387..cecf5f022 100644 --- a/content/intro-to-storybook/react/fr/get-started.md +++ b/content/intro-to-storybook/react/fr/get-started.md @@ -32,23 +32,16 @@ yarn Maintenant, nous pouvons vérifier rapidement que les différents environnements de notre application fonctionnent correctement: ```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 ``` -
-💡 Nous avons ajouté le drapeau --watchAll à notre commande de test, pour s'assurer que tous les tests sont effectués. Pendant que vous progressez dans ce tutoriel, vous serez exposés à différents scénarios de test. Vous pouvez aussi ajouter ce drapeau à votre script de test dans votre package.json . -
- -Notre application front-end se compose de trois modules: test automatisé (Jest), développement de composants (Storybook) et l'application elle-même. +Les principales modalités de notre application frontend : développement de composants (Storybook) et l'application elle-même. -![les 3 modules](/intro-to-storybook/app-three-modalities.png) +![Modalités principales](/intro-to-storybook/app-main-modalities-react.png) Selon la partie de l'application sur laquelle vous travaillez, vous voudriez peut-être exécuter un ou plusieurs de ces modules simultanément. Comme nous nous concentrerons actuellement sur la création d'un seul composant d'UI, nous continuerons à exécuter Storybook. diff --git a/content/intro-to-storybook/react/fr/screen.md b/content/intro-to-storybook/react/fr/screen.md index eb07ee04d..d908823db 100644 --- a/content/intro-to-storybook/react/fr/screen.md +++ b/content/intro-to-storybook/react/fr/screen.md @@ -113,12 +113,15 @@ const store = configureStore({ export default store; ``` -Maintenant que nous avons mis à jour notre store pour récupérer les données de l'API et que nous gérons les différents états de notre application, nous pouvons créer `InboxScreen.js` dans le dossier `src/components`: +Maintenant que nous avons mis à jour notre store pour récupérer les données de l'API et que nous gérons les différents états de notre application, nous pouvons créer `InboxScreen.jsx` dans le dossier `src/components`: + +```jsx:title=src/components/InboxScreen.jsx +import { useEffect } from 'react'; -```js:title=src/components/InboxScreen.js -import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; + import { fetchTasks } from '../lib/store'; + import TaskList from './TaskList'; export default function InboxScreen() { @@ -154,9 +157,12 @@ export default function InboxScreen() { Nous avons aussi besoin de changer le composant `App` pour rendre le `InboxScreen` (nous utiliserons à la fin un routeur pour choisir le bon écran, mais ne nous inquiétons pas de cela ici): -```diff:title=src/App.js -- import logo from './logo.svg'; -- import './App.css'; +```diff:title=src/App.jsx +- import { useState } from 'react' +- import reactLogo from './assets/react.svg' +- import viteLogo from '/vite.svg' +- import './App.css' + + import './index.css'; + import store from './lib/store'; @@ -164,22 +170,29 @@ Nous avons aussi besoin de changer le composant `App` pour rendre le `InboxScree + import InboxScreen from './components/InboxScreen'; function App() { +- const [count, setCount] = useState(0) return ( -
--
-- 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 +-

-
+ + @@ -191,11 +204,9 @@ export default App; Cependant, c'est dans le rendu de la story dans Storybook que les choses deviennent intéressantes. -Comme nous l'avons vu précédemment, le composant `TaskList` est un composant **connecté** et se fonde sur le store Redux pour rendre ses tâches. Comme `InboxScreen` est aussi un composant connecté, nous allons faire un travail similaire et fournir un store à la story. Voici comment rendre les stories dans `InboxScreen.stories.js`: - -```js:title=src/components/InboxScreen.stories.js -import React from 'react'; +Comme nous l'avons vu précédemment, le composant `TaskList` est un composant **connecté** et se fonde sur le store Redux pour rendre ses tâches. Comme `InboxScreen` est aussi un composant connecté, nous allons faire un travail similaire et fournir un store à la story. Voici comment rendre les stories dans `InboxScreen.stories.jsx`: +```jsx:title=src/components/InboxScreen.stories.jsx import InboxScreen from './InboxScreen'; import store from '../lib/store'; @@ -205,12 +216,12 @@ 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 = {}; ``` Nous pouvons constater une erreur dans la story de `error`. Au lieu d'afficher le bon état, il montre une liste de tâches. Une manière de corriger ce problème est de fournir une version simulée (qu'on appelle un "mock") de chaque étape, comme nous avons fait dans le chapitre précédent. Ici, pour nous aider à corriger cette erreur, nous allons utiliser une librairie de simulation d'API bien connue, grâce à un addon de Storybook. @@ -234,77 +245,79 @@ Ensuite, nous devons mettre à jour `.storybook/preview.js` et les initialiser: ```diff:title=.storybook/preview.js import '../src/index.css'; -+ // Registers the msw addon -+ import { initialize, mswDecorator } from 'msw-storybook-addon'; +// Registers the msw addon ++ import { initialize, mswLoader } from 'msw-storybook-addon'; -+ // Initialize MSW +// 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: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, }, }, ++ loaders: [mswLoader], }; + +export default preview; ``` Enfin, mettez à jour les stories de `InboxScreen` en incluant un [paramètre](https://storybook.js.org/docs/react/writing-stories/parameters) qui simule les appels à l'API: -```diff:title=src/components/InboxScreen.stories.js -import React from 'react'; - +```diff:title=src/components/InboxScreen.stories.jsx import InboxScreen from './InboxScreen'; + import store from '../lib/store'; -+ import { rest } from 'msw'; + ++ import { http, HttpResponse } from 'msw'; + + import { MockedState } from './TaskList.stories'; + import { Provider } from 'react-redux'; 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( -+ 'https://jsonplaceholder.typicode.com/todos?userId=1', -+ (req, res, ctx) => { -+ return res(ctx.json(MockedState.tasks)); -+ } -+ ), ++ http.get('https://jsonplaceholder.typicode.com/todos?userId=1', () => { ++ return HttpResponse.json(MockedState.tasks); ++ }), + ], + }, -+ }; ++ }, +}; -export const Error = Template.bind({}); -+ Error.parameters = { +export const Error = { ++ parameters: { + msw: { + handlers: [ -+ rest.get( -+ 'https://jsonplaceholder.typicode.com/todos?userId=1', -+ (req, res, ctx) => { -+ return res(ctx.status(403)); -+ } -+ ), ++ http.get('https://jsonplaceholder.typicode.com/todos?userId=1', () => { ++ return new HttpResponse(null, { ++ status: 403, ++ }); ++ }), + ], + }, -+ }; ++ }, +}; ```
-💡 En complément, une autre approche valable serait de passer la donnée à travers la hiérarchie des composants, d'autant plus si vous utilisez GraphQL. C'est comment nous avons construit Chromatic à travers plus de 800 stories. + +💡 En complément, une autre approche valable serait de passer la donnée à travers la hiérarchie des composants, d'autant plus si vous utilisez [GraphQL](http://graphql.org/). C'est comment nous avons construit [Chromatic](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook) à travers plus de 800 stories.
@@ -312,20 +325,20 @@ Regardez votre Storybook, et vous verrez que la story `error` fonctionne doréna -## Tests d'intéraction +## Tests des composants Jusque là, nous avons été capable de construire une application fonctionnelle, en construisant de simples composants jusqu'à un écran, en testant continuellement les changements grâce à nos stories. Mais chaque nouvelle story nécessite aussi une vérification manuelle sur les autres stories, pour s'assurer que l'interface utilisateur n'a pas été changé. Ceci donne beaucoup de travail supplémentaire. Ne pouvons-nous pas automatiser ce flux et tester nos interactions entre les composants de manière automatique? -### Ecrire un test d'interaction en utilisant la fonction play +### Écrire un test de composant en utilisant la fonction play -La fonction de Storybook [`play`](https://storybook.js.org/docs/react/writing-stories/play-function) et [`@storybook/addon-interactions`](https://storybook.js.org/docs/react/writing-tests/interaction-testing) nous aide à faire des tests d'interactions. Une fonction play inclue de petits exemples de code qui s'exécutent après que les stories se rendent. +La fonction [`play`](https://storybook.js.org/docs/writing-stories/play-function) de Storybook et [`@storybook/addon-interactions`](https://storybook.js.org/docs/writing-tests/component-testing) nous aident avec cela. Une fonction play inclut de petits extraits de code qui s'exécutent après le rendu de la story. La fonction play nous aide à vérifier les changements d'UI quand les tâches sont mises à jour. Elle utilise les API du DOM, agnostiques du type de librairie utilisé, ce qui signifie que nous pouvons écrire des stories avec cette fonction pour interagir avec l'UI et simuler des actions utilisateurs, quelque soit la librarie front utilisée. @@ -333,46 +346,42 @@ L'addon `@storybook/addon-interactions` nous aide à visualiser nos tests dans S Regardons cela! Mettez à jour votre nouvelle story `InboxScreen`, et créer les interactions avec le composant en ajoutant le code suivant: -```diff:title=src/components/InboxScreen.stories.js -import React from 'react'; - +```diff:title=src/components/InboxScreen.stories.jsx import InboxScreen from './InboxScreen'; import store from '../lib/store'; -import { rest } from 'msw'; + +import { http, HttpResponse } from 'msw'; + import { MockedState } from './TaskList.stories'; + 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: [ + http.get('https://jsonplaceholder.typicode.com/todos?userId=1', () => { + return HttpResponse.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')); @@ -383,28 +392,35 @@ Default.parameters = { + // Simulates pinning the third task + await fireEvent.click(canvas.getByLabelText('pinTask-3')); + }); -+ }; - -export const Error = Template.bind({}); -Error.parameters = { - msw: { - handlers: [ - rest.get( - 'https://jsonplaceholder.typicode.com/todos?userId=1', - (req, res, ctx) => { - return res(ctx.status(403)); - } - ), - ], ++ }, +}; + +export const Error = { + parameters: { + msw: { + handlers: [ + http.get('https://jsonplaceholder.typicode.com/todos?userId=1', () => { + return new HttpResponse(null, { + status: 403, + }); + }), + ], + }, }, }; ``` +
+ +💡 Le package `@storybook/test` remplace les packages de test `@storybook/jest` et `@storybook/testing-library`, offrant une taille de bundle plus petite et une API plus simple basée sur le package [Vitest](https://vitest.dev/). + +
+ Regardez la story `Default` . Cliquer sur le section `Interactions` pour voir la liste des interactions de votre fonction play de votre story. @@ -440,9 +456,11 @@ yarn test-storybook --watch ```
-💡 Les tests d'interactions avec la fonction play sont une manière fantastique de tester vos composants d'UI. Ils peuvent faire plein plus que ce que nous avons parcouru. Nous vous encourageons à lire la documentation officielle pour apprendre plus à ce sujet. -
-Pour creuser encore plus les tests, vous pouvez lire Le livre du test. Il agrège les stratégies de tests utilisées par les équipes front reconnues afin de vous faire accélérer votre flux de développement. + +💡 Les tests d'interactions avec la fonction play sont une manière fantastique de tester vos composants d'UI. Ils peuvent faire plein plus que ce que nous avons parcouru. Nous vous encourageons à lire la [documentation officielle](https://storybook.js.org/docs/writing-tests/component-testing) pour en savoir plus. + +Pour creuser encore plus les tests, vous pouvez lire le [Guide des Tests](/ui-testing-handbook). Il agrège les stratégies de tests utilisées par les équipes front reconnues afin de vous faire accélérer votre flux de développement. +
![Le lanceur de test Storybook a lancé tous les tests](/intro-to-storybook/storybook-test-runner-execution.png) @@ -465,5 +483,5 @@ Nous avons commencé du bas avec une `Task`, puis progressé avec `TaskList`, et Nous n'avons pas encore terminé - le travail ne s'arrête pas à la construction de l'UI. Nous devons également veiller à ce qu'elle reste durable dans le temps.
-💡 N'oubliez pas de commiter vos changements avec git! +💡 N'oubliez pas de commiter vos changements avec git !
diff --git a/content/intro-to-storybook/react/fr/simple-component.md b/content/intro-to-storybook/react/fr/simple-component.md index 2c3205c1d..646adfae1 100644 --- a/content/intro-to-storybook/react/fr/simple-component.md +++ b/content/intro-to-storybook/react/fr/simple-component.md @@ -20,18 +20,16 @@ Lorsque nous commençons à construire une `Task`, nous écrivons d'abord nos te ## Préparer le terrain -Tout d'abord, créons la composante "tâche" et le fichier story qui l'accompagne : `src/components/Task.js` et `src/components/Task.stories.js`. +Tout d'abord, créons la composante "tâche" et le fichier story qui l'accompagne : `src/components/Task.jsx` et `src/components/Task.stories.jsx`. Nous commencerons par une implémentation standard de `Task`, en prenant simplement en compte les attributs dont nous savons que nous en aurons besoin et les deux actions que vous pouvez entreprendre sur une tâche (pour la déplacer entre les listes) : -```js:title=src/components/Task.js -import React from 'react'; - +```jsx:title=src/components/Task.jsx export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { return (
-
); @@ -42,44 +40,62 @@ Ci-dessus, nous rendons un balisage simple pour `Task` fondé sur la structure H Ci-dessous, nous allons construire les trois états de test de Task dans le fichier story: -```js:title=src/components/Task.stories.js -import React from 'react'; +```jsx:title=src/components/Task.stories.jsx +import { fn } from "@storybook/test"; import Task from './Task'; +export const ActionsData = { + onArchiveTask: fn(), + onPinTask: fn(), +}; + export default { component: Task, title: 'Task', + tags: ['autodocs'], + //👇 Our exports that end in "Data" are not stories. + excludeStories: /.*Data$/, + args: { + ...ActionsData, + }, }; -const Template = args => ; - -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', + }, }, }; ``` +
+ +💡 Les [**Actions**](https://storybook.js.org/docs/essentials/actions) vous aident à vérifier les interactions lors de la construction des composants UI en isolation. Souvent, vous n'aurez pas accès aux fonctions et à l'état que vous avez dans le contexte de l'application. Utilisez `fn()` pour les simuler. + +
+ Il y a deux niveaux d'organisation de base dans Storybook: le composant et ses stories enfants. Considérez chaque story comme une permutation d'un composant. Vous pouvez avoir autant de story par composant que vous en voulez. - **Composant** @@ -89,24 +105,21 @@ Il y a deux niveaux d'organisation de base dans Storybook: le composant et ses s Pour informer Storybook sur le composant que nous documentons, nous créons un export `default` qui contient: -- `component`: le composant lui-même -- `title`: Comment faire référence au composant dans la barre latérale de l'application Storybook - -Pour définir nos stories, nous exportons une fonction pour chacun de nos états tests afin de générer une story. La story est une fonction qui renvoie un élément qui a été rendu (c'est-à-dire un composant avec un ensemble de props) dans un état donné --exactement comme un [Composant fonctionnel](https://reactjs.org/docs/components-and-props.html#function-and-class-components). +- `component` -- le composant lui-même +- `title` -- comment faire référence au composant dans la barre latérale de Storybook +- `tags` -- pour générer automatiquement la documentation de nos composants +- `excludeStories` -- informations supplémentaires requises par la story mais qui ne doivent pas être rendues dans Storybook +- `args` -- définir les actions [args](https://storybook.js.org/docs/essentials/actions#action-args) que le composant attend pour simuler les événements personnalisés -Comme nous avons plusieurs permutations de notre composant, il est pratique de lui assigner une variable `Template`. L'introduction de ce schéma dans vos stories réduira la quantité de code que vous devez écrire et maintenir. - -
-💡 Template.bind({}) est une technique standard JavaScript permettant de faire une copie d'une fonction. Nous utilisons cette technique pour permettre à chaque story exportée de définir ses propres propriétés, mais en utilisant la même implémentation. -
+Pour définir nos stories, nous utiliserons le Component Story Format 3 (également connu sous le nom de [CSF3](https://storybook.js.org/docs/api/csf)) pour construire chacun de nos cas de test. Ce format est conçu pour élaborer nos cas de test de manière concise. En exportant un objet contenant chaque état du composant, nous pouvons définir nos tests de manière plus intuitive et créer et réutiliser les stories plus efficacement. Les arguments ou [`args`](https://storybook.js.org/docs/react/writing-stories/args) pour faire court, nous permettent d'éditer en temps réel nos composants avec l'addon de contrôles sans avoir à redémarrer Storybook. Quand la valeur d'un [`args`](https://storybook.js.org/docs/react/writing-stories/args) change, le composant change aussi. -Lors de la création d'une story, nous utilisons un argument de base `task` pour construire la forme de la tâche que le composant attend. Cette tâche est généralement modélisée à partir de ce à quoi ressemblent les données réelles. Encore une fois, `exporter` cette forme nous permettra de la réutiliser dans des stories ultérieures, comme nous le verrons. +`fn()` nous permet de créer un callback qui apparaît dans le panneau **Actions** de l'interface Storybook lorsqu'on clique dessus. Ainsi, lorsque nous construisons un bouton d'épingle, nous pourrons déterminer si un clic de bouton est réussi dans l'interface. -
-💡 Les actions vous aident à vérifier les interactions lors de la construction des composants de l'UI en isolation. Souvent, vous n'aurez pas accès aux fonctions et à l'état dont vous disposez dans le contexte de l'application. Utilisez action() pour les simuler. -
+Comme nous devons passer le même ensemble d'actions à toutes les permutations de notre composant, il est pratique de les regrouper dans une seule variable `ActionsData` et de les passer dans notre définition de story à chaque fois. Un autre avantage du regroupement des `ActionsData` dont un composant a besoin est que vous pouvez les `export` et les utiliser dans les stories des composants qui réutilisent ce composant, comme nous le verrons plus tard. + +Lors de la création d'une story, nous utilisons un argument de base `task` pour construire la forme de la tâche que le composant attend. Cette tâche est généralement modélisée à partir de ce à quoi ressemblent les données réelles. Encore une fois, `export` cette forme nous permettra de la réutiliser dans des stories ultérieures, comme nous le verrons. ## Configuration @@ -115,27 +128,22 @@ Nous devrons apporter quelques modifications à la configuration du Storybook po Commencez par modifier le fichier de configuration de votre Storybook (`.storybook/main.js`) comme suit: ```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', ], - framework: '@storybook/react', - core: { - builder: '@storybook/builder-webpack5', - }, - features: { - interactionsDebugger: true, + framework: { + name: '@storybook/react-vite', + options: {}, }, }; +export default config; ``` Après avoir effectué la modification ci-dessus, dans le dossier `storybook`, changez votre `preview.js` en ce qui suit: @@ -144,26 +152,28 @@ Après avoir effectué la modification ci-dessus, dans le dossier `storybook`, c + 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: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, }, }, }; -``` -Les [`parameters`](https://storybook.js.org/docs/react/writing-stories/parameters) sont généralement utilisés pour contrôler le comportement des fonctionnalités et des addons de Storybook. Dans notre cas, nous allons les utiliser pour configurer la manière dont les `actions` (les callbacks simulés) sont gérées. +export default preview; +``` -Les `actions` nous permettent de créer des callbacks qui apparaissent dans la section **actions** de Storybook lorsqu'on clique dessus. Ainsi, lorsque nous construisons un bouton d'épingle, nous pourrons déterminer si un clic de bouton est réussi dans l'interface. +Les [`parameters`](https://storybook.js.org/docs/writing-stories/parameters) sont généralement utilisés pour contrôler le comportement des fonctionnalités et des addons de Storybook. Dans notre cas, nous ne les utiliserons pas à cette fin. À la place, nous importerons le fichier CSS de notre application. Une fois que nous aurons fait cela, le redémarrage du serveur Storybook devrait permettre d'obtenir des cas de test pour les trois états de Task: @@ -174,14 +184,12 @@ Maintenant que nous avons configuré Storybook, importé des styles et élaboré Le composant est encore basique pour le moment. Tout d'abord, écrivons le code qui permettra de réaliser la conception sans trop entrer dans les détails: -```js:title=src/components/Task.js -import React from 'react'; - +```jsx:title=src/components/Task.jsx export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { return (
-
-## Composant construit! +## Composant construit ! Nous avons maintenant réussi à créer un composant sans avoir besoin d'un serveur ou d'exécuter toute l'application front-end. L'étape suivante consiste à construire les autres composants de la Taskbox un par un, de manière similaire. @@ -322,7 +328,7 @@ Les tests d'accessibilité font référence à une pratique d'audit du DOM avec Storybook inclut un [addon d'accessibilité officiel](https://storybook.js.org/addons/@storybook/addon-a11y). Fondé sur [axe-core](https://github.com/dequelabs/axe-core) de Deque, il peut analyser jusqu'à [57% des problèmes WCAG](https://www.deque.com/blog/automated-testing-study-identifies-57-percent-of-digital-accessibility-issues/). -Regardons comment cela marche! Executez la commande suivante pour installer l'addon: +Regardons comment cela marche ! Executez la commande suivante pour installer l'addon: ```shell yarn add --dev @storybook/addon-a11y @@ -331,27 +337,27 @@ yarn add --dev @storybook/addon-a11y Ensuite, mettez à jour le fichier de configuration de 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', - }, - features: { - interactionsDebugger: true, + framework: { + name: '@storybook/react-vite', + options: {}, }, }; +export default config; ``` -![Problème d'accessibilité de Task dans Storybook](/intro-to-storybook/finished-task-states-accessibility-issue.png) +Enfin, redémarrez votre Storybook pour voir le nouvel addon activé dans l'UI. + +![Problème d'accessibilité de Task dans Storybook](/intro-to-storybook/finished-task-states-accessibility-issue-7-0.png) A travers nos stories, nous pouvons voir que l'addon a trouvé un problème d'accessibilité dans l'un de nos tests d'états. Le message [**"Elements must have sufficient color contrast"**](https://dequeuniversity.com/rules/axe/4.4/color-contrast?application=axeAPI) signifie qu'il n'y a pas assez de contraste entre le titre de la tâche et la couleur de fond. Nous pouvons rapidement corriger cela en changeant la couleur du texte vers un gris plus noir dans le CSS (situé dans `src/index.css`). @@ -363,8 +369,8 @@ A travers nos stories, nous pouvons voir que l'addon a trouvé un problème d'ac } ``` -Ca y est! Nous avons fait un premier pas pour rendre notre application accessible. En continuant à ajouter de la complexité à notre application, nous pouvons répéter ce process pour tous les autres composants sans besoin de rajouter des outils additionnels ou des environnements de tests. +Ca y est ! Nous avons fait un premier pas pour rendre notre application accessible. En continuant à ajouter de la complexité à notre application, nous pouvons répéter ce process pour tous les autres composants sans besoin de rajouter des outils additionnels ou des environnements de tests.
-💡 N'oubliez pas de commiter vos changements dans git! +💡 N'oubliez pas de commiter vos changements dans git !
diff --git a/content/intro-to-storybook/react/fr/test.md b/content/intro-to-storybook/react/fr/test.md index cb0d1439d..48414d625 100644 --- a/content/intro-to-storybook/react/fr/test.md +++ b/content/intro-to-storybook/react/fr/test.md @@ -43,14 +43,14 @@ Commencez par créer une nouvelle branche pour ce changement: git checkout -b change-task-background ``` -Modifiez `src/components/Task.js` comme suit: +Modifiez `src/components/Task.jsx` comme suit: -```diff:title=src/components/Task.js +```diff:title=src/components/Task.jsx export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { return (
-