Programming Journal

学習したことの整理用です。

Storybook 6.x から 7.0へアップグレードするときに詰まったこと

概要

StorybookをStorybook 6.x から 7.0へアップグレードしたときに、公式のMigrationガイドを元に進めたものの、テストがFlakyになってしまったり、落ちてしまったり、修正に苦労したのでまとめます。 アップグレード方法はざっとだけ触れます

全体のおおまかな流れ

  1. Storybook7へアップグレード
  2. 廃止されたaddonの削除
  3. main.ts の修正
  4. 廃止された型の修正
  5. @storybook/testing-reactアンインストール
  6. 詰まったところ

公式のMigration Guide

Migration guide for Storybook 7.0

Storybook7へアップグレード

pnpm dlx storybook@latest upgrade

https://storybook.js.org/docs/react/migration-guide#automatic-upgrade

deprecatedになったaddonの削除 / 新規addon追加

互換性がなくなったaddonがあるとAttentionがでるので削除する

一覧 Community outreach to upgrade addons to 7.0 · Issue #20529 · storybookjs/storybook · GitHub

main.tsの修正

main.jsはこちらを参考に Configure Storybook

Next.jsのroutingなど、addon不要になったのが個人的に嬉しかった🎉

Integrate Next.js and Storybook automatically

廃止された型の修正

storybook/MIGRATION.md at next · storybookjs/storybook · GitHub

ComponentStory, ComponentStoryObj, ComponentStoryFn and ComponentMeta types are deprecated

The type of StoryObj and StoryFn have been changed in 7.0 so that both the "component" as "the props of the component" will be accepted as the generic parameter.

ジェネリックパラメータとして、コンポーネント自体とコンポーネントのプロパティが受け入れられるようになった

import type { Story } from '@storybook/react';
import { Button, ButtonProps } from './Button';

// This works in 7.0, making the ComponentX types redundant.
const meta: Meta<typeof Button> = { component: Button };

export const CSF3Story: StoryObj<typeof Button> = { args: { label: 'Label' } };

export const CSF2Story: StoryFn<typeof Button> = (args) => <Button {...args} />;
CSF2Story.args = { label: 'Label' };

// Passing props directly still works as well.
const meta: Meta<ButtonProps> = { component: Button };

export const CSF3Story: StoryObj<ButtonProps> = { args: { label: 'Label' } };

export const CSF2Story: StoryFn<ButtonProps> = (args) => <Button {...args} />;
CSF2Story.args = { label: 'Label' };

@storybook/testing-reactアンインストール

This package is deprecated as it is a core functionality of Storybook 7! · Issue #143 · storybookjs/testing-react · GitHub

Hey there! I wanted to thank you for using @storybook/testing-react!

@storybook/testing-react has been promoted to a first-class Storybook functionality in Storybook 7. This means that you no longer need this package, and this package will not be worked on anymore (especially regarding Storybook 6, unless there are security issues). Instead, you can import the same utilities, but from the @storybook/react package. Additionally, the internals of composeStories and composeStory have been revamped, so the way a story is composed is way more accurate, and it's possible this issue doesn't happen there anymore.

Please do the following:

Upgrade to Storybook 7 if you haven't already Uninstall @storybook/testing-react Update your imports from @storybook/testing-react to @storybook/react

@storybook/testing-reactは同じユーティリティを@storybook/reactパッケージからインポートできるようになったので、不要になる。アンインストールする。

// Component.test.jsx
- import { composeStories } from '@storybook/testing-react';
+ import { composeStories } from '@storybook/react';

// setup-files.js
- import { setProjectAnnotations } from '@storybook/testing-react';
+ import { setProjectAnnotations } from '@storybook/react';

詰まったところ

上記の一通りの更新作業を終えたところ、StoryをJestで再利用しているのですが、Storybookのplay関数を使ってテストしている箇所や、userEventのsetup関数を使っている箇所が尽く落ちるようになってしまった。

コードは公式より引用

export const InputFieldFilled: Story<InputFieldProps> = {
  play:  ({ canvasElement }) => {
    const canvas = within(canvasElement);
    userEvent.type(canvas.getByRole('textbox'), 'Hello world!');
  },
};
const { InputFieldFilled } = composeStories(stories);

test('renders with play function', async () => {
  const { container } = render(<InputFieldFilled />);

  // pass container as canvasElement and play an interaction that fills the input
  await InputFieldFilled.play({ canvasElement: container });

  const input = screen.getByRole('textbox') as HTMLInputElement;
  expect(input.value).toEqual('Hello world!');
});

対処方法

非同期処理に修正

-  play: ({ canvasElement }) => {
+  play: async ({ canvasElement }) => {
     const canvas = within(canvasElement)
-    userEvent.type(canvas.getByRole('textbox'), 'Hello world!')
+    await userEvent.type(canvas.getByRole('textbox'), 'Hello world!')
   },
 }

関連ライブラリを全部最新化 storybookだけアップデートしたものの、testing-library周りを最新化していなかった しばらくupdateしていなかったもの↓ これでplay関数周りは治った

   "@testing-library/dom": "9.3.3",
   "@testing-library/jest-dom": "6.1.3",
   "@testing-library/user-event": "14.5.1",

testing-library/jest-domのv6 アップデートについて詰まったところ

testing-library/jest-dom をv6系にupdateすると、型を手動で取り込む必要があるので、グローバルな型定義ファイル(global.d.ts)にマッチャーの型定義を手動でインポートして定義する必要があった。 公式issue ※公式issueではこの問題はv6でresolvedとあるが、できなかった

userEvent.typeで詰まったところ

userEvent.type not workingのissue issue おそらくこのissueと同じ?でuserEvent.typeを使用している箇所がflakyになってしまった。

↓公式のこの書き方だと、user.typeもoKだったけど、setup関数化して使っているところはflakyになる

Introduction | Testing Library

import userEvent from '@testing-library/user-event'

// setup function
function setup(jsx) {
  return {
    user: userEvent.setup(),
    // Import `render` from the framework library of your choice.
    // See https://testing-library.com/docs/dom-testing-library/install#wrappers
    ...render(jsx),
  }
}

test('render with a setup function', async () => {
  const {user} = setup(<MyComponent />)
  // ...
})
   const user = userEvent.setup()
   // Import `render` and `screen` from the framework library of your choice.
   // See https://testing-library.com/docs/dom-testing-library/install#wrappers
   render(<MyComponent />)