María Simó Front—end developer

Lo nuevo de la nueva versión de Storybook

Storybook está a punto de liberar una nueva mayor después de dos años, que incluye importantes cambios en la escritura de historias, nuevas formas de documentación y multitud de posibilidades para hacer testing dentro de la propia herramienta.

Scroll to Content

Created 10 months ago

Status

Draft, I'm still learning about this

Qué es Storybook

Storybook es una herramienta para el desarrollo, documentación y testeo de la UI de nuestra aplicación. Las UIs modernas están formadas por componentes, y Storybook ha impulsado una filosofía de Component Driven Development. Permite desarrollar nuestros componentes en un entorno aislado de la aplicación, para asegurar su calidad desde todos los ángulos posibles.

Es también la herramienta idónea para la documentación de sistemas de diseño, porque se sitúa como una fuente de verdad perfecta en la comunicación entre diseñadores y desarrolladores.

Ha pasado de ser una herramienta de nicho a un estándar de la industria para el desarrollo de UIs.


Cambios fundamentales de la nueva versión

Los storybooks dentro de nuestras aplicaciones son mucho más extensos y sofisticados que hace algunos años. Por eso el equipo de Storybook ha orientado sus esfuerzos a mejorar la ergonomía de la herramienta en esta nueva versión. En tres aspectos:

  • Un diseño renovado de la interfaz
  • Una manera más simple de escribir historias
  • Mejor soporte de Typescript

Un diseño renovado de la interfaz

En cuanto a la renovación del diseño, pasa por un cambio en el set de iconos, el rediseño y consolidación de los menus flotantes y otro montón de ajustes que vienen a mejorar el aspecto general y la experiencia de uso de la interfaz. El cambio más notable es la desaparición del tab de "Docs" en el menu superior de las historias, para integrarse en el menu lateral, como veremos en el apartado de Documentación.

Una manera más simple de escribir historias

La sintaxis con la que escribimos historias en el entorno de Storybook se conoce como Component Story Format (en adelante, CSF). Storybook 7 implementa una nueva versión de CSF, CSF3, más ligera, que nos permite escribir historias con mucho menos código.

En la nueva versión de Storybook, la sintáxis de la historia pasa a ser simplemente en un objeto, sin necesidad de ninguna declaración adicional.

1// En Storybook 6
2export default {
3  title: 'Components/Button',
4  component: Button,
5};
6
7export const Primary = (args) => <Button {...args} />;
8Primary.args = { primary: true };
9
10// En Storybook 7
11export default { component: Button };
12export const Primary = { args: { primary: true } };

La exportación por defecto (líneas 2 y 11) se conoce como meta. Especifica cómo es el componente que estamos creando a rasgos generales. El named export (líneas 7 y 12) es la historia y especifica aquellos inputs que crean un estado del componente que estamos interesados en documentar.

En CFS2 era necesaria una función de renderizado para cada una de las historias (línea 7). En CSF3 ya no hace falta. La sintáxis de la historia pasa a ser simplemente en un objeto, sin necesidad de ninguna declaración adicional.

Aunque las historias aceptan una propiedad render, por si queremos sobreescribir algún comportamiento concreto del componente para una historia en particular. Lo haríamos así:

1export const Primary = {
2  render: (args) => <Button {...args} specialProp={specialProp}/>
3  args: { primary: true }
4}

Ahora que las historias son puramente objetos, podemos extenderlas a partir de una historia anterior usando spread.

1export const Tertiary = {
2  ...Primary,
3  args: { primary: true },
4};

Otra novedad interesante es que, a partir de ahora, podemos omitir el atributo titleen el meta de nuestras historias. Típicamente, empleamos title para indicar el título y la ubicación de la historia en el árbol de contenidos.

Ahora Storybook es capaz de leer la ubicación de nuestros archivos y mapear la estructura de directorios de nuestra app. Es capaz de reproducir tal cual el árbol de archivos que vemos en nuestro IDE. De esta manera, podemos dejar de pensar en cómo organizar las historias y obtenemos una experiencia más consistente entre nuestro IDE y Storybook.

![Fuente "Component Story Format 3 is here", Storybook blog](/images/storybook-18-03-2023/storybook-tree.png "Fuente "Component Story Format 3 is here", Storybook blog")

Mejor soporte para Typescript

Un problema habitual en el tipado de las historias ha sido que Storybook no era capaz de lanzar un error cuando no pasábamos propiedades requeridas al componente en la historia. La ayuda que recibíamos de Typescript era muy limitada dentro del ámbito de Storybook, por no poseer un tipado fuerte.

Esta limitación viene dada porque las historias pueden recibir parte de sus argumentos a través de su meta y otra parte a través de la propia historia. Por ejemplo:

1export type Props = {
2  planName: string;
3  price: number;
4  planDescription: string;
5};
6
7export default {
8  component: PlanCard,
9  title: 'General/PlanCard',
10  args: {
11    planName: 'Intensive',
12  },
13} as Meta;
14
15const Template: Story<Props> = (args) => <PlanCard {...args} />;
16
17export const Default = Template.bind({});
18Default.args = {
19  // deberíamos tener un error aquí, porque price es requerido
20  planDescription: 'Zero to conversational in a month.',
21};

En este caso, tan habitual, la propiedad args de la historia viene tipada como Partial<Props>, de modo que no es posible saber qué propiedades "faltan".

Ahora, la versión 7 incluye dos tipos, Meta y StoryObj, con los que va a ser posible tener autocompletado y errores cuando las historias no cumplan el contrato de tipado del componente. La manera más simple de tipar una historia con estos tipos sería la siguiente:

1const meta: Meta<typeof Button> = {
2  component: Button,
3};
4export default Meta;
5
6type Story = StoryObj<typeof Button>;
7export const Primary: Story = { args: { primary: true } };

Para el caso que mencionamos, donde los args están repartidos entre el meta y las stories, tenemos que tipar de otra manera, para que Typescript sea capaz de seguir el hilo. Tomamos ventaja del nuevo operador de Typescript, satisfies.

1
2import { PlanCard } from ".";
3import { Meta, StoryObj } from "@storybook/react";
4
5const meta = {
6  component: PlanCard,
7  args: {
8    planName: "Intensive"
9  }
10} satisfies Meta<typeof PlanCard>
11
12export default meta
13
14export const Default: StoryObj<typeof meta> = {
15  args: {
16    // ahora vamos a tener autocompletado y errores
17   planDescription: 'Zero to conversational in a month.',
18  }
19};
20
Autocompletado en las historias de Storybook 7

Autocompletado en las historias de Storybook 7

Errores en las historias de Storybook 7

Errores en las historias de Storybook 7

Como vemos en las imágenes, ahora sí vamos a tener opciones de autocompletado y detección de errores. El tipado de args es una unión mucho más compleja. Vemos que planName es opcional, porque Typescript entiende que ya lo hemos especificado en el meta.

📎 CodeSandox con código de ejemplo

1// Éste es el tipado que Typescript es capaz de inferir ahora
2Partial<{
3    planName: string;
4    price: number;
5    planDescription: string;
6}> & {
7    price: number;
8    planDescription: string;
9    planName?: string | undefined;
10}
11

Documentación en Storybook 7

Como apuntábamos arriba, los Docs cambian su ubicación. Ya no se encuentran en el menu superior de cada historia, sino que pasan a formar parte del árbol de contenidos, como la primera de las historias de un componente. Es decir, conceptualmente, los docs se mueven desde un nivel de historia a un nivel de componente. Este movimiento responde a una intención por parte del equipo de Storybook de que seamos más conscientes de este recurso.

La mayoría de las veces nuestros componentes son auto-descriptivos pero, en determinadas situaciones, sí vamos a querer documentar más cuidadosamente. Por ejemplo, si estamos trabajando en un sistema de diseño o nuestro Storybook va a ser consultado y consumido por diferentes stakeholders y necesitamos ser más exhaustivos. En ese caso, tenemos los docs a nuestra disposición.

![Fuente "Storybook 7 Docs", Storybook blog](/images/storybook-18-03-2023/sb-7-docs.png "Fuente "Storybook 7 Docs", Storybook blog")

Éstas son las distintas opciones que tenemos para documentar nuestros componentes:

  • Autodocs
  • Documentación personalizada

Autodocs

El autodoc es una plantilla que se genera automáticamente para cada uno de nuestros archivos, e incluye ejemplos y descripciones de las historias que creamos para el componente. Es una feature opcional. Si queremos generar autodocs, tenemos que indicarlo de forma explícita. Lo hacemos pasando una prop al objeto meta:

1const meta = {
2  component: Button,
3  tags: ['autodocs'],
4};

Si queremos ir un paso más allá, podemos personalizar los autodocs. Lo hacemos de dos maneras:

  • Añadiendo comentarios con JsDoc, que se convierten en las descripciones de nuestra historia en la documentación.
  • Usando las opciones de parameters.docs.
1/** This is the description of my story */
2export const Primary: Story = {
3  args: {
4    primary: true,
5  },
6  parameters: {
7    docs: {
8      canvas: { sourceState: 'shown' },
9    },
10  },
11};

Documentación personalizada

Si, en cambio, lo que buscamos es un control total sobre la documentación, podemos usar MDX. MDX es markdown con la posibilidad de renderizar componentes. No es una sintáxis propia de Storybook, sino un estándar de la industria. Storybook usa MDX2. Con MDX podemos crear páginas de documentación independientes, para introducir nuestro sistema, o bien asociadas a nuestra historia.

📎 Más sobre la documentación en Storybook


Testing en Storybook 7

Storybook puede considerarse una herramienta de testeo en sí misma. Pero, además, ha ido incluyendo más y más integraciones que permiten hacer diferentes tipos de tests directamente en el marco de la herramienta, incluidos tests unitarios y de integración. Esto parece una manera bastante interesante de concentrar los esfuerzos de testing en un solo punto de la aplicacion, particulamente para equipos especializados en el desarrollo de UI. Podemos ahorrarnos configurar y hacer convivir, con su inevitable solapado, diferentes herramientas de testeo

Para crear un test de componente, necesitamos tres pasos:

  • Aislar el componente y preparar un test case
  • Simular las interacciones con herramientas como Testing Library
  • Ejecutar aserciones con herramientas como Jest

Muchas veces tenemos que hacer un montón de trabajo previo para renderizar los componentes de manera aislada en nuestros tests (mockear providers, routers, datos...), cuando en Storybook ya lo tenemos hecho. Además en el entorno de Node, donde corren nuestros test, no recibimos ningún feedback visual cuando algo va mal.

Storybook Interaction Tests permite escribir tests directamente dentro de las stories y ejecutarlos en el browser. Cada una de las historias que creamos es en sí misma un test case, donde el primer paso es renderizar y comprobar que todo marcha como esperamos.

Después, podemos usar la nueva propiedad de story play y escribir el test directamente en nuestra historia. En el test, simulamos la interacción del usuario en el browser y hacer aserciones. Storybook nos provee de wrappers para Jest y Testing library que hacen posible su uso en el entorno del browser.

1import { within, fireEvent } from '@storybook/testing-library';
2import { expect } from '@storybook/jest';
3
4export const Default = {
5  play: async ({ canvasElement }) => {
6    const canvas = within(canvasElement);
7
8    await fireEvent.click(canvas.getRoleBy('button'));
9
10    await expect(canvas.getByText('Are you sure?')).toBeInTheDocument();
11  },
12};

Añadir tests con Jest y Testing Library es una manera de amplificar la experiencia de testeo que ya supone escribir historias.

Para visualizar el resultado, tenemos un nuevo panel, Interactions, donde podemos emular los pasos de la interacción y debuguear nuestros tests.

Fuente "Component Story Format 3 is here", Storybook blog

Fuente "Component Story Format 3 is here", Storybook blog

Para que esta manera de testear sea una opción viable, necesitamos poder integrar las pruebas en el pipeline de integración continua de la aplicación. Storybook nos proporciona un test runner que transforma todas las interacciones a nivel de historia en tests que podemos correr en modo headless. Incluye opciones para generar informes de cobertura. Además, cuando un test falla, te vincula directamente a la historia de Storybook para poder visualizar el error.

Aunque funciona con Playwright por detrás, lo que nos obliga en cierta medida a familiarizarnos con esta herramienta y añadirla a nuestro stack, es una opción que merece la pena explorar.

Storybook cuenta con toda una sección de documentación sobre testing.


¿Cuándo empezamos?

Actualmente la versión estable de Storybook sigue siendo la 6.5. y el equipo está puliendo los últimos detalles para la release. Pero podemos empezar a probar nueva versión si instalamos storybook@next. Si queremos empezar a pensar en la migración a la nueva versión, Storybook facilita una guía con los cambios.

Aunque la nueva versión presenta numerosos cambios interesantes, ya sólo por el ahorro de tiempo y código que va a suponer el nuevo formato para escribir historias, estoy impaciente por tener la oportunidad de integrarla en un nuevo proyecto.

Tell me what you think. I would love to receive your feedback.

Would you like to invite me to give a talk about this at your event? Is there some topic of your interest you want me to write about?

Drop me a message