はじめに
前回までの記事の続きです。(シリーズ化予定)
▪️実装Step
Step1:基本編
Step2:Coin機能実装編 ←いまここ
Step3: 仕様追加、リファクタリング実施
Step4:ワイルドカード、セブンカードの追加
Step5:ディレクトリ整理/リファクタリング
成果物
追加仕様
・Coinは最初100枚。
・1回で3枚消費する。
・勝利すると10枚コインが追加される。
プロダクトコード
src/components/SlotMachine.tsx
import React, { useState } from "react";
import { Reel } from "./Reel";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
const symbols = ["🍒", "🍋", "🍊", "🍇", "🍉"];
export const SlotMachine: React.FC = () => {
const [coins, setCoins] = useState(100);
const [reels, setReels] = useState(Array(3).fill(symbols[0]));
const handleSpin = () => {
const newReels = reels.map(
() => symbols[Math.floor(Math.random() * symbols.length)]
);
setReels(newReels);
setCoins((prev) => prev - 3);
// 当たり判定
if (new Set(newReels).size === 1) {
setCoins(coins - 3 + 10); // 当たりならコインを追加
}
};
const isWin = new Set(reels).size === 1;
return (
<Box sx={{ textAlign: "center", marginTop: 4 }}>
<Box>Coinの枚数:{coins}枚</Box>
<Box sx={{ display: "flex", justifyContent: "center", gap: 2 }}>
{reels.map((symbol, index) => (
<Reel key={index} symbol={symbol} />
))}
</Box>
<Button
variant="contained"
color="primary"
style={{ marginTop: "16px" }}
onClick={handleSpin}
disabled={coins < 3}
>
スピン
</Button>
{isWin && <Box sx={{ marginTop: 2 }}>勝利!</Box>}
</Box>
);
};
テストコード
src/components/SlotMachine.test.tsx
import React from "react";
import { render, fireEvent, screen } from "@testing-library/react";
import { SlotMachine } from "./SlotMachine";
describe("SlotMachine コンポーネント", () => {
test("初期レンダリングでスピンボタンが表示される", () => {
render(<SlotMachine />);
expect(screen.getByRole("button", { name: "スピン" })).toBeInTheDocument();
});
test("スピンボタンをクリックするとリールが回転する", () => {
render(<SlotMachine />);
const initialReels = screen
.getAllByText("🍒")
.map((reel) => reel.textContent);
fireEvent.click(screen.getByRole("button", { name: "スピン" }));
const updatedReels = screen
.getAllByText(/🍒|🍋|🍊|🍇|🍉/)
.map((reel) => reel.textContent);
expect(updatedReels).not.toEqual(initialReels);
});
});
describe("コインの消費", () => {
test("スピンボタンをクリックするとコインが3枚消費される", () => {
render(<SlotMachine />);
fireEvent.click(screen.getByRole("button", { name: "スピン" }));
expect(screen.getByText("Coinの枚数:97枚")).toBeInTheDocument();
});
test("コインが3枚以上あるときはスピンボタンをクリックできる", () => {
render(<SlotMachine />);
expect(screen.getByRole("button", { name: "スピン" })).toBeEnabled();
});
test("コインが0枚になるとスピンボタンは無効になる", () => {
render(<SlotMachine />);
const spinButton = screen.getByRole("button", {
name: "スピン",
}) as HTMLButtonElement;
// コインを0枚にするまでボタンをクリックする
while (!spinButton.disabled) {
fireEvent.click(spinButton);
}
// ボタンが無効化されていることを検証
expect(spinButton).toBeDisabled();
});
});
実行結果
~/develop/react/react_slot_machine$ yarn test src/components/SlotMachine.test.tsx
PASS src/components/SlotMachine.test.tsx
SlotMachine コンポーネント
✓ 初期レンダリングでスピンボタンが表示される (44 ms)
✓ スピンボタンをクリックするとリールが回転する (16 ms)
コインの消費
✓ スピンボタンをクリックするとコインが3枚消費される (9 ms)
✓ コインが3枚以上あるときはスピンボタンをクリックできる (7 ms)
✓ コインが0枚になるとスピンボタンは無効になる (31 ms)
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 0.449 s, estimated 1 s
Ran all test suites matching /src\/components\/SlotMachine.test.tsx/i.
まだ大した仕様を入れていないのに、段々と複雑化してきましたね。
コンポーネントに責務を持たせた関係で、コンポーネントの可読性・保守性が低下。
また、テストも検証がしにくいケースが増えてきました。もうちょっとすると開発がしんどくなり、開発生産性が爆下がりしてきます。どこかでリファクタリングを検討する段階に入りそうです😊