# Bun: Testing Storybook ShadcnUI with Vite React

Continuing blog, for Bun from the last post; [Bun.sh with React, Typescript, TailwindCSS and Storybook](https://linkb.hashnode.dev/bunsh-with-react-typescript-tailwindcss-and-storybook)

In this blog, we will explore the basics of Storybook testing with its runner.

### Getting Started

We will be following this installation documentation from [Storybook Test Runner](https://storybook.js.org/docs/react/writing-tests/test-runner)

Start by installing the add-on

```bash
bun add -d @storybook/test-runner
```

and add new scripts to the `package.json`

```json
"scripts": {
    "test-storybook": "test-storybook"
}
```

Since the underlying of this package is using Jest and Playwright - we need to run our code first before running the test.

Run the storybook first in the first terminal

```bash
bun storybook
```

Run the test on the second terminal

```bash
bun test-storybook
```

Running this command directly will be a smoke test for every story in the storybook. This is useful to check if there are any errors in mounting or rendering the components.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695693656723/e3adc8b5-35b9-4d0f-841d-4843b8e2a29b.png align="center")

### Adding Form for Interaction

In this example, we will add a Form component using the [ShadcnUI Form](https://ui.shadcn.com/docs/components/form) and Input component.

```bash
bunx shadcn-ui@latest add form input
```

For illustration purposes, we will just use the example from the ShadcnUI Profile Form component. Add this ProfileForm component to the `components` folder.

```typescript
// components/ProfileForm.tsx
'use client'

import { zodResolver } from '@hookform/resolvers/zod'
import * as z from 'zod'

import { Button } from '@/components/ui/button'
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { useForm } from 'react-hook-form'

const formSchema = z.object({
  username: z.string().min(2, {
    message: 'Username must be at least 2 characters.',
  }),
})

export function ProfileForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      username: '',
    },
  })

  const onSubmit = form.handleSubmit((values) => {
    // Do something with the form values.
    // ✅ This will be type-safe and validated.
    console.log(values)
  })

  return (
    <Form {...form}>
      <form onSubmit={onSubmit} className='space-y-8'>
        <FormField
          control={form.control}
          name='username'
          render={({ field }) => (
            <FormItem>
              <FormLabel>Username</FormLabel>
              <FormControl>
                <Input placeholder='shadcn' {...field} />
              </FormControl>
              <FormDescription>
                This is your public display name.
              </FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type='submit'>Submit</Button>
      </form>

      {form.formState.isSubmitted && (
        <div className='p-4 bg-green-100 rounded-md'>
          <p className='text-green-800'>
            Your profile has been updated successfully.
          </p>
        </div>
      )}
    </Form>
  )
}
```

After we have created the `ProfileForm` component, we can create a new story in the Storybook.

```typescript
// stories/ProfileForm.stories.ts
import type { Meta, StoryObj } from '@storybook/react'

import { ProfileForm } from '@/components/ProfileForm'

const meta = {
  title: 'Example/ProfileForm',
  component: ProfileForm,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
  argTypes: {},
} satisfies Meta<typeof ProfileForm>

export default meta
type Story = StoryObj<typeof meta>

export const Primary: Story = {
  args: {},
}
```

We can check this in the Storybook it will show like below.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695694678387/0916dc35-057d-412a-a214-a861c22177ce.png align="center")

### Interactive Testing in Storybook

For the interaction test, we will follow this [guideline](https://storybook.js.org/docs/react/writing-tests/interaction-testing) from Storybook documentation.

Install the library needed for this test.

```json
bun add -d @storybook/testing-library @storybook/jest @storybook/addon-interactions
```

Now comes the interesting part, writing your user interaction test code. Create a new story for the Filled form.

```typescript
export const Filled: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement)

    // 👇 Simulate interactions with the component
    await userEvent.type(canvas.getByTestId('email'), 'email@provider.com')

    await userEvent.type(canvas.getByTestId('password'), 'a-random-password')

    // See https://storybook.js.org/docs/react/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
    await userEvent.click(canvas.getByRole('button'))

    // 👇 Assert DOM structure
    await expect(
      canvas.getByText(
        'Everything is perfect. Your account is ready and we should probably get you started!'
      )
    ).toBeInTheDocument()
  },
}
```

add the import lines as well

```typescript
import { userEvent, within } from '@storybook/testing-library'
import { expect } from '@storybook/jest'
```

I faced a type error while writing the test.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695695915651/f6bb1b1f-d1cb-4e08-a6dc-07dede4849f0.png align="center")

hence add this to the `compilerOptions` in the `tsconfig.json`

```json
"types": ["@testing-library/jest-dom"]
```

Running again the storybook and checking the Filled story of ProfileForm, there will be a Pass test in the interaction tab.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695696588799/2f25985f-3369-4bbc-a168-05959be2acdd.png align="center")

With this, we can even move back to the previous state and play the interaction on our own in the Storybook UI.

We can test again the test-storybook command and see the results of this new interaction test in the test runner.

```typescript
bun test-storybook --verbose
```

Running in verbose mode `--verbose`, so we can see that the Filled story is different than the rest of the stories here which is "play-test" instead of "smoke-test".

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695696726121/a2791534-68d6-437d-b51a-cf9c40fccba5.png align="center")

### Final Thoughts

Testing is a huge subject to cover - we only cover a small portion of it.

After going through this post, testing with Bun and Storybook in Vite React App seems working fine.

---

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">At the time of writing this post, we are mostly still running with NodeJS to execute the command.</div>
</div>

Following [this documentation](https://bun.sh/docs/cli/run#bun), we can try to run the Storybook in bun runtime as well like so

```typescript
bun --bun storybook
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695697955074/3b49dd07-654c-4b5e-aaac-5b14bc3af27c.png align="center")

However, it is still a [tracked issue](https://github.com/oven-sh/bun/issues/3794) currently in the Bun Repository - therefore we shall await more stability coming into the Bun runtime in the coming weeks.
