🙆♀️
Remix, Storybookでフロントエンドのテストを試してみる
前提条件
node: v20.11.0
Remixセットアップ
プロジェクト作成
npx create-remix@latest
npx create-remix@latest
Need to install the following packages:
create-remix@2.13.1
Ok to proceed? (y) y
remix v2.13.1 💿 Let's build a better website...
dir Where should we create your new project?
./my-remix-app <- 好きなプロジェクト名 ... ①
◼ Using basic template See https://remix.run/guides/templates for more
✔ Template copied
git Initialize a new git repository?
Yes
deps Install dependencies with npm?
Yes
✔ Dependencies installed
✔ Git initialized
done That's it!
Enter your project directory using cd ./my-remix-app
Check out README.md for development and deploy instructions.
Join the community at https://rmx.as/discord
npm notice
npm notice New minor version of npm available! 10.8.3 -> 10.9.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.9.0
npm notice To update run: npm install -g npm@10.9.0
npm notice
依存関係のインストール
cd my-remix-app/ ①のプロジェクト名
npm install
動作確認
npm run dev
npm run dev
> dev
> remix vite:dev
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
local: のURLにアクセスする
Storybookセットアップ
Storybookインストール
npx sb init
(エラーで落ちる)
❯ npx sb init
╭──────────────────────────────────────────────────────╮
│ │
│ Adding Storybook version 8.3.6 to your project.. │
│ │
╰──────────────────────────────────────────────────────╯
• Detecting project type. ✓
Installing dependencies...
up to date, audited 815 packages in 759ms
253 packages are looking for funding
run `npm fund` for details
7 low severity vulnerabilities
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
• Adding Storybook support to your "React" app • Detected Vite project. Setting builder to Vite. ✓
✔ Getting the correct version of 10 packages
Configuring Storybook ESLint plugin at .eslintrc.cjs
✔ Installing Storybook dependencies
. ✓
Installing dependencies...
up to date, audited 997 packages in 795ms
313 packages are looking for funding
run `npm fund` for details
7 low severity vulnerabilities
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
attention => Storybook now collects completely anonymous telemetry regarding usage.
This information is used to shape Storybook's roadmap and prioritize features.
You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
https://storybook.js.org/telemetry
╭──────────────────────────────────────────────────────────────────────────────╮
│ │
│ Storybook was successfully installed in your project! 🎉 │
│ To run Storybook manually, run npm run storybook. CTRL+C to stop. │
│ │
│ Wanna know more about Storybook? Check out https://storybook.js.org/ │
│ Having trouble or want to chat? Join us at https://discord.gg/storybook/ │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
Running Storybook
> storybook
> storybook dev -p 6006 --initial-path=/onboarding --quiet
@storybook/core v8.3.6
=> Failed to build the preview
Error: The Remix Vite plugin requires the use of a Vite config file
at configResolved (./node_modules/@remix-run/dev/dist/vite/plugin.js:724:15)
at async Promise.all (index 2)
at async resolveConfig (file://./node_modules/vite/dist/node/chunks/dep-BWSbWtLw.js:66404:3)
at async getOptimizeDeps (./node_modules/@storybook/builder-vite/dist/index.js:58:2906)
at async createViteServer (./node_modules/@storybook/builder-vite/dist/index.js:58:3531)
at async Module.start (./node_modules/@storybook/builder-vite/dist/index.js:58:4345)
at async storybookDevServer (./node_modules/@storybook/core/dist/core-server/index.cjs:47328:11)
at async buildOrThrow (./node_modules/@storybook/core/dist/core-server/index.cjs:46581:12)
at async buildDevStandalone (./node_modules/@storybook/core/dist/core-server/index.cjs:48518:78)
at async withTelemetry (./node_modules/@storybook/core/dist/core-server/index.cjs:47080:12)
WARN Broken build, fix the error above.
WARN You may need to refresh the browser.
✔ Would you like to help improve Storybook by sending anonymous crash reports? … yes
vite設定
viteのコンフィグファイルを作成
touch vite-sb.config.ts
// vite-sb.config.ts
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [
tsconfigPaths(),
],
});
.storybook/main.ts でviteのプラグインパスを指定
import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
stories: [
"../stories/**/*.mdx",
"../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
],
addons: [
"@storybook/addon-onboarding",
"@storybook/addon-links",
"@storybook/addon-essentials",
"@chromatic-com/storybook",
"@storybook/addon-interactions",
],
framework: {
name: "@storybook/react-vite",
options: {
builder: { // 追加
viteConfigPath: './vite-sb.config.ts', // 追加
} // 追加
},
},
};
export default config;
.storybook/preview.ts を修正してオンボーディング無効化
import type { Preview } from "@storybook/react";
const preview: Preview = {
parameters: {
previewTabs: { // 追加
'storybook/docs/panel': { index: -1 }, // 追加
}, // 追加
options: { // 追加
showPanel: false, // 追加
}, // 追加
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;
動作確認
Storybook起動
npm run storybook
http://localhost:6006 にアクセス
コンポーネント作成からミニマムなテストまで
コンポーネント追加
app/routes/Counter.tsx を新規作成
import { useState } from "react";
interface CounterProps {
initialValue?: number;
}
export const Counter = ({ initialValue = 0 }: CounterProps) => {
const [count, setCount] = useState(initialValue);
const containerStyle = {
display: "flex",
alignItems: "center",
gap: "10px",
padding: "10px",
backgroundColor: "#f0f4f8",
borderRadius: "8px",
width: "fit-content",
margin: "20px auto",
};
const buttonStyle = {
fontSize: "1.5rem",
padding: "5px 10px",
backgroundColor: "#4CAF50",
color: "white",
border: "none",
borderRadius: "5px",
cursor: "pointer",
};
const displayStyle = {
fontSize: "1.5rem",
padding: "5px 15px",
backgroundColor: "#ffffff",
color: "#333",
border: "1px solid #ddd",
borderRadius: "5px",
minWidth: "50px",
textAlign: "center" as const,
};
return (
<div style={containerStyle}>
<button
type="button"
id="plus"
style={buttonStyle}
onClick={() => setCount(count + 1)}
>
+
</button>
<span id="count" style={displayStyle}>{count}</span>
<button
type="button"
id="minus"
style={buttonStyle}
onClick={() => setCount(count - 1)}
>
-
</button>
</div>
);
};
StoryBookの設定に対象となるパスを追加
.storybook/main.ts
const config: StorybookConfig = {
stories: [
"../app/**/*.mdx", // 追加
"../app/**/*.stories.@(js|jsx|mjs|ts|tsx)", // 追加
"../stories/**/*.mdx",
"../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
],
StoryBookのシナリオを追加
app/routes/Counter.stories.ts を追加
// 最低限必要
import type { Meta, StoryObj } from '@storybook/react';
import { Counter } from './Counter';
const meta = {
title: 'Example/Counter',
component: Counter,
} satisfies Meta<typeof Counter>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {args: { initialValue: 0 }};
// こっからは追加
export const One: Story = {args: { initialValue: 1 }};
StoryBookでコンポーネントテスト実行
npm install
npm install -D @storybook/testing-library @storybook/jest
テストコード追加
app/routes/Counter.stories.ts にテストコードを追加
import * as testingLibrary from '@storybook/testing-library'; // 追加
const { userEvent, within } = testingLibrary; // 追加
import { expect } from "@storybook/jest"; // 追加
import type { Meta, StoryObj } from '@storybook/react';
import { Counter } from './Counter';
const meta = {
title: 'Example/Counter',
component: Counter,
} satisfies Meta<typeof Counter>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {args: { initialValue: 0 }};
export const One: Story = {args: { initialValue: 1 }};
// 以降を追加
export const Interactions: Story = {
args: {
initialValue: 5,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const plusButton = canvas.getByRole('button', { name: '+' });
const minusButton = canvas.getByRole('button', { name: '-' });
const countDisplay = canvas.getByText('5');
// プラスボタンをクリックしてカウントが増えるか確認
await userEvent.click(plusButton);
expect(countDisplay.textContent).toBe('6');
// マイナスボタンをクリックしてカウントが減るか確認
await userEvent.click(minusButton);
expect(countDisplay.textContent).toBe('5');
},
};
テスト実行
StoryBookの画面で結果を確認できる
Discussion