DEV Community

Matheo Daninos for SensioLabs

Posted on

How to transform Component Development with Storybook and Symfony UX ?

Hey everyone! I am so excited about this article because what I'm going to show you here has been my dream since I first heard about Symfony UX! I will demonstrate a setup that makes me incredibly productive, but most importantly, brings me a lot of joy.

As you know, I love working with components (TwigComponent/LiveComponent), and I also adore Storybook! In my last article, I showed you how to use Storybook to share your components with your team. Today, we're going to dive even deeper. Storybook has been a game changer for me primarily because it provides the best environment to work with components. Components are visual and interactive, and Storybook offers a fantastic playground to view, interact with, and test your components. It helps you create beautiful, interactive, fun, and robust components. So, let’s see how this works!

Working in isolation

If you remember from my first article, we discussed the four main rules of component architecture. One of these rules is independence. Your components should not depend on the context of the page; you should be able to move your component from one page to another without any issues.

The great thing about using Storybook is that it enforces this rule. Storybook operates by isolating each component, allowing you to test them one by one in complete isolation. This ensures that if your component works in Storybook, it will work seamlessly on all your pages.

Hot Reload

So when I am working on a new component, the first thing I do is create a story. A really basic one that just gives me the environment to start building.



import Alert from '../templates/components/Alert.html.twig';
import { twig } from '@sensiolabs/storybook-symfony-webpack5';

export default {
  component: (args) => ({
    components: {Alert},
    template: twig`
      <twig:Alert>
        {{ message }}
      </twig:Alert>
  `
  }),
}

export const Default = {}


Enter fullscreen mode Exit fullscreen mode

Then I can just focus on creating my component. With hot reload, I get quick feedback, which makes things really comfortable for a visual component like an alert.

Show storybook hot reload

No need to press F5 anymore or try your component on random pages. Just write a story for your Storybook, and you will have a nice development environment.

Interactions

When working on a new feature, one of the most frustrating aspects can be having to interact with your component to access the part you want to test. For example, I have a small form here, and I want to see how it looks when the entered email is invalid.

Form in storybook

Doing this manually can be a huge waste of time, especially for larger components. That's why I don’t do that anymore! With Storybook, you can automate your interactions to reach the exact state you want to test

How can I do that ?

First, I set up my Default story:



import Email from '../templates/components/Email.html.twig';
import { twig } from '@sensiolabs/storybook-symfony-webpack5';

export default {
  component: (args) => ({
    component: {Email},
    template: twig`
        <twig:Email/>
    `,
  })
}


export const Default = {}


Enter fullscreen mode Exit fullscreen mode

Then, I add a new story called ‘WrongEmail’:



import Email from '../templates/components/Email.html.twig';
import { twig } from '@sensiolabs/storybook-symfony-webpack5';
import {userEvent, waitFor, within, expect, fn} from "@storybook/test";

...

export const Default = {}

export const WrongEmail = {
  play: async () => {
      ...
  }
}


Enter fullscreen mode Exit fullscreen mode

In this story, I define a play function. This function contains snippets of code executed after the story renders, allowing you to interact with your components and test scenarios that would otherwise require manual intervention.

The play function looks like this:



import {userEvent, waitFor, within, expect, fn} from "@storybook/test";

play: async ({canvasElement}) => {
    const canvas = within(canvasElement);

    await userEvent.type(canvas.getByLabelText('Email'), 'wrongemail');
    await userEvent.type(canvas.getByLabelText('FirstName'), 'Kobe');
    await userEvent.type(canvas.getByLabelText('LastName'), 'Bryant');

    await userEvent.click(canvas.getByRole('button'));
  }


Enter fullscreen mode Exit fullscreen mode

Storybook provides a wrapper around https://testing-library.com/.

If you are not familiar with this library, it is widely used in the JS community, very robust, and has a strong community around it, making it easy to find good resources.

So, if we get back to our play function, we see that we define an argument canvasElement. This canvasElement represents the canvas where our story is rendered.

Then we do the following:



const canvas = within(canvasElement);


Enter fullscreen mode Exit fullscreen mode

Here, we wrap our canvas in an object to enable better assertions later.



await userEvent.type(canvas.getByLabelText('Email'), 'wrongemail');


Enter fullscreen mode Exit fullscreen mode

This line simulates the user typing ‘wrongemail’ in the input with the label Email.

We do the same thing for the first name and last name:



await userEvent.type(canvas.getByLabelText('FirstName'), 'Kobe');
await userEvent.type(canvas.getByLabelText('LastName'), 'Bryant');


Enter fullscreen mode Exit fullscreen mode

Then we click on the submit button:



await userEvent.click(canvas.getByRole('button'));


Enter fullscreen mode Exit fullscreen mode

And just like this, Storybook will perform the interactions for me, so I no longer need to do all these steps by hand.

interactions storybook

You can also easily debug what happens using the interaction panel and go back to previous steps.

step by step in storybook

I just love this feature—it saves me so much time. Components are visual and interactive, and having an environment that lets me see and interact with my components easily is a real game changer.

And you know what? We can do even more! We can use Storybook to test our components!

Testing

I have a component, RadioList, that displays a list of radios and a search bar. When the user types into the search bar, the component updates the list of radios accordingly.

radio component in storybook

I know the content of my database, so I want to ensure that when I type "90," four radios are displayed.

We already know how to do this by writing a play function and adding an assertion at the end.



import {twig} from "@sensiolabs/storybook-symfony-webpack5";
import {userEvent, waitFor, within, expect} from "@storybook/test";

export default {
  component: (args) => ({
    template: twig`
      <twig:RadioList />
    `
  }),
}

export const Default = {
};

export const Play = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    await userEvent.type(canvas.getByRole('searchbox'), '90');

    await waitFor(() => {
      expect(canvas.queryAllByRole('listbox')).toHaveLength(4)
    });
  },
}


Enter fullscreen mode Exit fullscreen mode

And just like that, I have a real test that fully tests my LiveComponent from PHP to JavaScript!

I can see that my test is working by checking the interaction panel. I now have a green "Pass" indicator.

Tests passes

We can go even further and run all our tests at once by running:



npm run test-storybook


Enter fullscreen mode Exit fullscreen mode

What's interesting is that even simple stories with no interaction are tests. Storybook checks that all your components are rendered correctly. This is just a test runner, so you can completely run all your tests in your CI.

By leveraging Storybook for testing, you ensure a seamless and efficient development process, catching issues early and maintaining high-quality components.

Leveraging Storybook has transformed the way I develop and test components. From working in isolation to hot reloading, automating interactions, and writing comprehensive tests, Storybook provides a development environment that boosts productivity and fun. Although we still rely on Node.js, it’s not a big deal since Storybook is only used in your development environment and not deployed to production.

Components are often visual elements of your application, and maintaining a visual development approach greatly enhances the process. Storybook ensures that your components are robust, interactive, and beautifully designed. I hope you enjoyed this article and found it helpful. See you soon!

Top comments (1)

Collapse
 
reubenwalker64 profile image
Reuben Walker, Jr.

Great stuff!