LoginSignup
20
14

More than 3 years have passed since last update.

番外編:Storybook で UI コンポーネントをカタログ化する

Last updated at Posted at 2020-03-30

本編はこちらです。


Storybook というフレームワークを導入すると、作成した UI コンポーネントをカタログのように管理することができます。
image.png
UI コンポーネントをカタログ化することで再利用が促進されますし、例えばアプリを操作して特定の手順を踏まないと表示されないような UI コンポーネントも簡単に確認できるようになります。
また、カタログ化された各コンポーネントのスクリーンショットを記録することができるため、視覚情報としての差分検出も可能です。

カタログにはコンポーネント毎に登録を行う必要がありますが、必要な作業は MDX (Markdown と JSX を組み合わせて書ける) ファイルの作成だけで特別な操作は不要ですし、本記事では MDX の雛形をコードスニペット化してしまいますので本当に簡単に作成できます。

本題に入る前に

せっかくカタログ化を行うので、ここで下記のようなフォルダ階層[^を導入してみます。ついでに新しく Message コンポーネントも用意してしまいます。

  • [src]
    • [client]
      • [pages]
        • Home.tsx
        • Home.spec.tsx
        • HomeWork.tsx
        • HomeWork.spec.tsx
      • [parts]
        • Message.tsx
        • Message.spec.tsx
      • App.tsx
      • App.spec.tsx
      • App.styl
      • favicon.ico
      • index.html
      • index.tsx

pages フォルダと parts フォルダを作成し、Home.* と HomeWork.* を pages フォルダに移動してください。

App.tsxApp.spec.tsx は下記のようにリンクを修正します。

App.tsx
import * as React from "react";
import { Switch, Route, Link } from "react-router-dom";

import * as styles from "./App.styl";
import Home from "./pages/Home";
import HomeWork from "./pages/HomeWork";

const App = () => {
    return (
        <>
            <Link className={styles.menuButton} to="/">Home</Link>
            <Link className={styles.menuButton} to="/homework">Home Work</Link>
            <Switch>
                <Route exact path="/" component={Home} />
                <Route exact path="/homework" component={HomeWork} />
            </Switch>
        </>
    );
};

export default App;

App.spec.tsx
import * as React from "react";
import { MemoryRouter } from "react-router-dom";
import { render, cleanup, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";

const homeMock = jest.fn(() => <></>);
jest.mock("./pages/Home", () => ({ __esModule: true, default: homeMock } as any));

const homeworkMock = jest.fn(() => <></>);
jest.mock("./pages/HomeWork", () => ({ __esModule: true, default: homeworkMock } as any));

import App from "./App";

afterEach(cleanup);
afterEach(jest.clearAllMocks);
afterAll(() => {
    jest.unmock("./pages/Home");
    jest.unmock("./pages/HomeWork");
});

describe("App", () => {
    it("最初に Home が表示されること", () => {
        render(<MemoryRouter><App /></MemoryRouter>);
        expect(homeMock).toBeCalled();
    });

    it("メニューから Home を表示できること", () => {
        const root = render(<MemoryRouter><App /></MemoryRouter>);
        expect(homeMock).toBeCalled();
        homeMock.mockClear();

        const menuHomeButton = root.getByText("Home", { exact: true });
        fireEvent.click(menuHomeButton);

        expect(homeMock).toBeCalled();
    });

    it("メニューから Home Work を表示できること", () => {
        const root = render(<MemoryRouter><App /></MemoryRouter>);
        expect(homeworkMock).not.toBeCalled();

        const menuHomeWorkButton = root.getByText("Home Work", { exact: true });
        fireEvent.click(menuHomeWorkButton);

        expect(homeworkMock).toBeCalled();
    });
});

Home.tsx と HomeWork.tsx は下記のように Message コンポーネントを使用するよう修正します。

pages/Home.tsx
import * as React from "react";
import Message from "../parts/Message";

const Home = () => {
    return (
        <>
            <Message message="Hello World." />
        </>
    );
};

export default Home;

pages/HomeWork.tsx
import * as React from "react";
import { useState, useEffect } from "react";
import Message from "../parts/Message";

const HomeWork = () => {
    const [message, setMessage] = useState("Loading...");
    useEffect(() => {
        fetch("/api/hello")
            .then(response => response.json())
            .then(json => setMessage(json.message));
    }, []);
    return (
        <>
            <Message message={message} />
        </>
    );
};

export default HomeWork;

Message.tsx と Message.spec.tsx は下記の内容で作成してください。

parts/Message.tsx
import * as React from "react";

interface MessageProps {
    /** メッセージテキスト。 */
    message: string;
}

const Message = (props: MessageProps) => {
    return (
        <h1>{props.message}</h1>
    );
};

export default Message;

parts/Message.spec.tsx
import * as React from "react";
import { render, cleanup } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";

import Message from "./Message";

afterEach(cleanup);
afterEach(jest.clearAllMocks);

describe("Message", () => {
    it("メッセージが表示されること", () => {
        const root = render(<Message message="Hello." />);
        const heading = root.getByRole("heading");
        expect(heading).toHaveTextContent("Hello.");
    });
});

準備が整いました。
ここからが本題となります。

開発コンテナの設定変更

Dockerfile

スナップショットはヘッドレス Chrome (GUI としての表示を行わずにバックグラウンドで Chrome を起動するモード) を使用して記録されます。
Chrome をインストールするよう Dockerfile を変更しておきます。

.devcontainer/Dockerfile ファイルを下記の内容に書き換えてください。

FROM node:12.16-buster-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
    #
    # Verify git, process tools installed
    && apt-get -y install --no-install-recommends git openssh-client iproute2 procps \
    #
    #####
    # https://github.com/Microsoft/vscode-dev-containers/tree/master/containers/docker-in-docker#how-it-works--adapting-your-existing-dev-container-config
    # Note that no recommended packages are required, except for gnupg-agent.
    #
    # Install Docker CE CLI
    && apt-get install -y --no-install-recommends apt-transport-https ca-certificates curl software-properties-common lsb-release jq \
    && apt-get install -y gnupg-agent \
    && curl -fsSL https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/gpg | apt-key add - 2>/dev/null \
    && add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]') $(lsb_release -cs) stable" \
    && apt-get update \
    && apt-get install -y --no-install-recommends docker-ce-cli \
    #
    # Install Docker Compose
    && curl -sSL "https://github.com/docker/compose/releases/download/$(curl https://api.github.com/repos/docker/compose/releases/latest | jq .name -r)/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose \
    && chmod +x /usr/local/bin/docker-compose \
    #
    #####
    # Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
    # Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
    # installs, work.
    && curl -fsSL https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get update \
    && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf --no-install-recommends \
    #
    #####
    # Clean up
    && apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*
ENV DEBIAN_FRONTEND=dialog

devcontainer.json

Storybook は Web アプリケーションです。ポート6006を使用するのでポートフォワーディング設定を追加します。

.devcontainer/devcontainer.json ファイルの forwardPorts に下記を追加してください。

        6006, // Storybook server

MDX 形式ファイル用の拡張機能 (シンタックスハイライトのみですが) も追加します。

.devcontainer/devcontainer.json ファイルの extensions に下記を追加してください。

        "silvenon.mdx",

リビルド

開発コンテナの設定を変えましたので、一度リビルド を行ってください。

パッケージの設定変更

Node モジュールのインストール

下記のコマンドにて、Storybook 関係のモジュールをインストールします。

npm i -D \
    @storybook/cli \
    @storybook/react \
    @storybook/addons \
    @storybook/addon-actions \
    @storybook/addon-docs \
    @storybook/addon-knobs \
    @storybook/addon-links \
    @storybook/addon-storyshots \
    @storybook/addon-storyshots-puppeteer \
    @storybook/addon-viewport \
    react-docgen-typescript-loader \
    @babel/core \
    babel-loader \
    puppeteer \
    @types/puppeteer

スクリプトの変更

package.json ファイルの scripts を下記の内容に書き換えてください。(test コマンドの変更及び test-snapshots コマンド、accept-snapshots コマンド、storybook コマンドの追加。)

    "scripts": {
        "start": "            export NODE_ENV=production  && ts-node ./src/server/server.ts",
        "start:dev": "        export NODE_ENV=development && ts-node-dev --nolazy --inspect=9229 ./src/server/server.ts",
        "build": "            export NODE_ENV=production  && webpack --config ./webpack.config.ts",
        "build:dev": "        export NODE_ENV=development && webpack --config ./webpack.config.ts",
        "run": "              export NODE_ENV=production  && npm run build     && npm start",
        "run:dev": "          export NODE_ENV=development && npm run build:dev && npm run start:dev",
        "run:hmr": "          export NODE_ENV=development && export HMR=true   && concurrently \"npm run start:dev\" \"webpack-dev-server --config ./webpack.config.hmr.ts\"",
        "test": "             export NODE_ENV=test        && jest --coverage --testPathIgnorePatterns=src/client/storyshots/",
        "test-snapshots": "   export NODE_ENV=test        && jest src/client/storyshots/",
        "accept-snapshots": " export NODE_ENV=test        && jest -u src/client/storyshots/",
        "storybook": "        export NODE_ENV=development && start-storybook -p 6006"
    },

Git Ignore の変更

.gitignore ファイルの末尾に下記を追加してください。(先頭ではなく末尾です。)

storybook-static/
__snapshots__/
__image_snapshots__/
!.vscode/*.code-snippets

Docker Ignore の変更

.dockerignore ファイルに下記を追加してください。

**/storybook-static
**/__snapshots__
**/__image_snapshots__

Storybook の設定

基本設定 (main.ts)

.storybook/main.ts ファイルを作成し、下記の内容で保存してください。

import { StorybookConfig } from "@storybook/core/types";
import myConfig from "../webpack.config";


module.exports = <StorybookConfig>{
    stories: ["../src/client/**/*.stories.mdx"],
    addons: [
        "@storybook/addon-actions",
        "@storybook/addon-docs",
        "@storybook/addon-knobs",
        "@storybook/addon-links",
        "@storybook/addon-viewport",
    ],
    webpackFinal: (config, options) => {
        config.resolve = config.resolve ?? {};
        config.resolve.extensions = config.resolve.extensions ?? [];
        config.resolve.modules = config.resolve.modules ?? [];
        config.module = config.module ?? { rules: [] };
        return {
            ...config,
            resolve: {
                ...config.resolve,
                extensions: [
                    ...config.resolve.extensions,
                    ...myConfig.resolve!.extensions!,
                ],
                modules: [
                    ...config.resolve.modules,
                    ...myConfig.resolve!.modules!,
                ],
            },
            module: {
                ...config.module,
                rules: [
                    ...config.module.rules,
                    myConfig.module!.rules!.find(rule => (rule.test as RegExp)?.source === /\.tsx?$/.source)!,
                    myConfig.module!.rules!.find(rule => (rule.test as RegExp)?.source === /\.styl$/.source)!,
                ],
            },
            performance: false,
        };
    }
};

リバースプロキシの設定 (middleware.ts)

.storybook/middleware.js ファイルを作成し、下記の内容で保存してください。(.ts ではなく .js です。.ts は非対応のようです。)

const proxy = require("http-proxy-middleware");

module.exports = router =>
    router.use(
        "^/api",
        proxy({
            target: "http://localhost:3000"
        })
    );

スナップショットテストの設定

Jest 設定

jest.config.json ファイルを下記の内容に書き換えてください。

{
    "preset": "ts-jest",
    "modulePaths": [
        "src"
    ],
    "moduleNameMapper": {
        "\\.(css|styl)$": "<rootDir>/node_modules/jest-css-modules"
    },
    "transform": {
        "\\.mdx$": "@storybook/addon-docs/jest-transform-mdx",
        "\\.jsx?$": [
            "babel-jest",
            {
                "presets": [
                    "@babel/preset-env",
                    "@babel/preset-react"
                ]
            }
        ]
    },
    "coveragePathIgnorePatterns": [
        "/node_modules/",
        "/\\.storybook/",
        "\\.stories\\."
    ]
}

スナップショットテストの作成

src/client/storyshots/default.spec.js ファイルを作成し、下記の内容で保存してください。

import initStoryshots from "@storybook/addon-storyshots";
import { imageSnapshot } from "@storybook/addon-storyshots-puppeteer";

initStoryshots({
    suite: "Image storyshots",
    test: imageSnapshot({
        storybookUrl: "http://localhost:6006/",
        beforeScreenshot: (page, options) => {
            return new Promise(resolve => setTimeout(() => resolve(), 1000));
        }
    })
});

コードスニペットの設定

.vscode/storybook.code-snippets ファイルを作成し、下記の内容で保存してください。

{
    "Body": {
        "scope": "markdown",
        "prefix": "sb/body",
        "body": [
            "import { Meta, Story, Preview, Props } from \"@storybook/addon-docs/blocks\";",
            "import * as knobs from \"@storybook/addon-knobs\";",
            "import { action } from \"@storybook/addon-actions\";",
            "import { linkTo } from \"@storybook/addon-links\";",
            "import { MemoryRouter } from \"react-router-dom\";",
            "import ${TM_FILENAME_BASE/\\..*//} from \"./${TM_FILENAME_BASE/\\..*//}\";",
            "",
            "",
            "<Meta title=\"${TM_DIRECTORY/(.*)(?<=src\\/client)(\\/?)(.*)/$3$2/}${TM_FILENAME_BASE/\\..*//}\" component={${TM_FILENAME_BASE/\\..*//}} />",
            "",
            "",
            "# ${TM_FILENAME_BASE/\\..*//}",
            "",
            "## 概要",
            "",
            "$1${TM_FILENAME_BASE/\\..*//} の概要。",
            "",
            "",
            "## サンプル",
            "",
            "<Preview withSource=\"open\">",
            "    <Story name=\"default\">",
            "        <MemoryRouter>",
            "            <${TM_FILENAME_BASE/\\..*//} />",
            "        </MemoryRouter>",
            "    </Story>",
            "</Preview>",
            "",
            "",
            "## プロパティ",
            "",
            "<Props of={${TM_FILENAME_BASE/\\..*//}} />",
            "",
        ]
    },
    "Preview": {
        "scope": "markdown",
        "prefix": "sb/preview",
        "body": [
            "<Preview withSource=\"open\">",
            "    <Story name=\"${1}\">",
            "        <MemoryRouter>",
            "            <${TM_FILENAME_BASE/\\..*//} />",
            "        </MemoryRouter>",
            "    </Story>",
            "</Preview>",
        ]
    },
    "Text": {
        "scope": "mdx",
        "prefix": "sb/prop/text",
        "body": [
            "${1:name}={knobs.text(\"${1:name}\", \"${2:default value}\")}",
        ]
    },
    "Boolean": {
        "scope": "mdx",
        "prefix": "sb/prop/boolean",
        "body": [
            "${1:name}={knobs.boolean(\"${1:name}\", ${2|true,false|})}",
        ]
    },
    "Number": {
        "scope": "mdx",
        "prefix": "sb/prop/number",
        "body": [
            "${1:name}={knobs.number(\"${1:name}\", ${2:123})}",
        ]
    },
    "Slider": {
        "scope": "mdx",
        "prefix": "sb/prop/slider",
        "body": [
            "${1:name}={knobs.number(\"${1:name}\", ${2:123}, { range: true, min: ${3:0}, max: ${4:255}, step: ${5:1}})}",
        ]
    },
    "Color": {
        "scope": "mdx",
        "prefix": "sb/prop/color",
        "body": [
            "${1:name}={knobs.color(\"${1:name}\", \"${2:#ffffff}\")}",
        ]
    },
    "Object": {
        "scope": "mdx",
        "prefix": "sb/prop/object",
        "body": [
            "${1:name}={knobs.object(\"${1:name}\", ${2:{\\}})}",
        ]
    },
    "Array": {
        "scope": "mdx",
        "prefix": "sb/prop/array",
        "body": [
            "${1:name}={knobs.array(\"${1:name}\", ${2:[]}, \",\")}",
        ]
    },
    "Select box": {
        "scope": "mdx",
        "prefix": "sb/prop/select",
        "body": [
            "${1:name}={knobs.select(\"${1:name}\", ${2:{ Opt1: \"Option 1\", Opt2: \"Option 2\"\\}}, ${3:\"Option 1\"})}",
        ]
    },
    "Radio buttons": {
        "scope": "mdx",
        "prefix": "sb/prop/radios",
        "body": [
            "${1:name}={knobs.radios(\"${1:name}\", ${2:{ Opt1: \"Option 1\", Opt2: \"Option 2\"\\}}, ${3:\"Option 1\"})}",
        ]
    },
    "Options": {
        "scope": "mdx",
        "prefix": "sb/prop/options",
        "body": [
            "${1:name}={knobs.optionsKnob(\"${1:name}\", ${2:{ Opt1: \"Option 1\", Opt2: \"Option 2\"\\}}, ${3:\"Option 1\"}, { display: \"${4|radio,inline-radio,check,inline-check,select,multi-select|}\" })}",
        ]
    },
    "File": {
        "scope": "mdx",
        "prefix": "sb/prop/file",
        "body": [
            "${1:name}={knobs.files(\"${1:name}\", \"${2:.jpg, .png}\", [])}",
        ]
    },
    "Date": {
        "scope": "mdx",
        "prefix": "sb/prop/date",
        "body": [
            "${1:name}={knobs.date(\"${1:name}\", new Date(\"Jan 20 2017\"))}",
        ]
    },
}

MDX の作成

カタログには全ての UI コンポーネントを登録します。

App コンポーネント

src/client フォルダに App.stories.mdx ファイルを作成してください。
Ctrl + Space を押して sb/body を選択すると下記の内容が自動挿入されます。

import { Meta, Story, Preview, Props } from "@storybook/addon-docs/blocks";
import * as knobs from "@storybook/addon-knobs";
import { action } from "@storybook/addon-actions";
import { linkTo } from "@storybook/addon-links";
import { MemoryRouter } from "react-router-dom";
import App from "./App";


<Meta title="App" component={App} />


# App

## 概要

App の概要。


## サンプル

<Preview withSource="open">
    <Story name="default">
        <MemoryRouter>
            <App />
        </MemoryRouter>
    </Story>
</Preview>


## プロパティ

<Props of={App} />

概要を "アプリケーション。" に変更しておきます。

## 概要

アプリケーション。

Home コンポーネント

src/client/pages フォルダに Home.stories.mdx ファイルを作成してください。
Ctrl + Space を押して sb/body を選択すると下記の内容が自動挿入されます。

import { Meta, Story, Preview, Props } from "@storybook/addon-docs/blocks";
import * as knobs from "@storybook/addon-knobs";
import { action } from "@storybook/addon-actions";
import { linkTo } from "@storybook/addon-links";
import { MemoryRouter } from "react-router-dom";
import Home from "./Home";


<Meta title="pages/Home" component={Home} />


# Home

## 概要

Home の概要。


## サンプル

<Preview withSource="open">
    <Story name="default">
        <MemoryRouter>
            <Home />
        </MemoryRouter>
    </Story>
</Preview>


## プロパティ

<Props of={Home} />

こちらも概要を "Home ページ。" に変更しておいてください。

HomeWork コンポーネント

src/client/pages フォルダに HomeWork.stories.mdx ファイルを作成してください。
Ctrl + Space を押して sb/body を選択すると下記の内容が自動挿入されます。

import { Meta, Story, Preview, Props } from "@storybook/addon-docs/blocks";
import * as knobs from "@storybook/addon-knobs";
import { action } from "@storybook/addon-actions";
import { linkTo } from "@storybook/addon-links";
import { MemoryRouter } from "react-router-dom";
import HomeWork from "./HomeWork";


<Meta title="pages/HomeWork" component={HomeWork} />


# HomeWork

## 概要

HomeWork の概要。


## サンプル

<Preview withSource="open">
    <Story name="default">
        <MemoryRouter>
            <HomeWork />
        </MemoryRouter>
    </Story>
</Preview>


## プロパティ

<Props of={HomeWork} />

概要を "HomeWork ページ。" に変更しておいてください。

Message コンポーネント

src/client/parts フォルダに Message.stories.mdx ファイルを作成してください。
Ctrl + Space を押して sb/body を選択すると下記の内容が自動挿入されます。

import { Meta, Story, Preview, Props } from "@storybook/addon-docs/blocks";
import * as knobs from "@storybook/addon-knobs";
import { action } from "@storybook/addon-actions";
import { linkTo } from "@storybook/addon-links";
import { MemoryRouter } from "react-router-dom";
import Message from "./Message";


<Meta title="parts/Message" component={Message} />


# Message

## 概要

Message の概要。


## サンプル

<Preview withSource="open">
    <Story name="default">
        <MemoryRouter>
            <Message />
        </MemoryRouter>
    </Story>
</Preview>


## プロパティ

<Props of={Message} />

Message コンポーネントは message というプロパティを持っていて、このプロパティに設定されたテキストを UI 表示するようになっています。
このままですと message プロパティに何も設定していないため、カタログには何のメッセージも表示されません。
ここでプロパティに適当なテキストを設定すれば、勿論カタログに反映されます。それでも良いのですが、本記事では Knobs というアドオンを利用し、カタログ上で自由にテキストを設定できるよう対応させます。

対応させるには、プロパティを設定する際に knobs.text 関数を通して値を渡せば良いのですが、これもコードスニペットを用意してあります。
<Message /> タグの中にカーソルを移動し、Ctrl + Space を押して sb/props/text を選択してください。プロパティ名と初期値を入力する必要がありますので、プロパティ名を入力したら Tab キーを押してカーソルを移動させ初期値を入力します。

ezgif-4-72f05305211a.gif

import { Meta, Story, Preview, Props } from "@storybook/addon-docs/blocks";
import * as knobs from "@storybook/addon-knobs";
import { action } from "@storybook/addon-actions";
import { linkTo } from "@storybook/addon-links";
import { MemoryRouter } from "react-router-dom";
import Message from "./Message";


<Meta title="parts/Message" component={Message} />


# Message

## 概要

Message パーツ。


## サンプル

<Preview withSource="open">
    <Story name="default">
        <MemoryRouter>
            <Message message={knobs.text("message", "Hello.")} />
        </MemoryRouter>
    </Story>
</Preview>


## プロパティ

<Props of={Message} />

カタログの閲覧

Storybook の起動

Storybook を起動するには、Explorer サイドバーの NPM Scripts から、storybook を実行します。
image.png
Storybook も HMR (Hot Module Replacement) で動作するので、常時起動させておいて大丈夫です。

<注意>
HomeWork コンポーネントのようにサーバーサイドの API にアクセスするコンポーネントを正常に表示させるために、開発サーバーも起動 (run:hmr) させておく必要があります。

Storybook にアクセス

ブラウザで http://localhost:6006 にアクセスすると Storybook が開きます。
image.png
image.png

Message コンポーネントの message プロパティも変更可能になっています。
image.png

ちなみにプロパティの説明文は JS Doc から自動で読み込まれます。
image.png

スナップショットテスト

テスト実行

スナップショットテストを実行するには、Explorer サイドバーの NPM Scripts から、test-snapshots を実行します。 (予め Storybook を起動しておく必要があります。)
image.png

スナップショットがまだ記録されていない場合は記録が行われます。
test-snapshots を実行しスナップショットを記録したら、試しに Message.tsx を下記のように変更し、再び test-snapshots を実行してみてください。

import * as React from "react";

interface MessageProps {
    /** メッセージテキスト。 */
    message: string;
}

const Message = (props: MessageProps) => {
    return (
        <h1>Message: {props.message}</h1>
    );
};

export default Message;

表示されるテキストが増えたため差分が発生したことが検出されます。
image.png
差分を記録した画像ファイルへのリンクが一緒に出力されますので、Ctrl を押しながらクリックして画像を開き、差分を確認します。
image.png
差分が無い部分は白色、差分がある部分は赤色で表示されます。

スナップショットの更新

全ての差分画像について、意図した通りのものであることを確認できたら、accept-snapshots を実行しスナップショットを更新します。
image.png
image.png

おわりに

如何でしたでしょうか。
一度環境構築してしまえば、後は UI コンポーネント作成と同時に MDX ファイルを用意していくだけで、カタログ化・スナップショットテストが行えます。
どちらも恩恵は非常に大きい上に、本記事のようにコードスニペットを使えば MDX ファイルの作成も非常に簡単に行えます。
プロジェクトの規模が大きくなるにつれ Storybook の重要性は大きくなりますので、是非 Storybook 導入してみてください。


変更履歴

  • 2020/07/23: <BrowserRouter> ではカタログ内で History が共有されてしまうため <MemoryRouter> に変更。また、<MemoryRouter> が常に配置されるようコードスニペットを変更。その他細かな修正。
  • 2020/08/20: package.json 内の test 系スクリプト定義で NODE_ENVdevelopment ではなくより適切な test を設定するよう変更。
  • 2020/09/12: main.ts を、webpack.config の設定を参照する形に変更。
20
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
14