TL;DR
<button>をラップするだけのCarouselButtonコンポーネントを、TDD で 3 段階に分けて実装する。- 各段階で、先にテストを書き、それをパスする最低限の実装を書く。
内容
第3章の 1 つ目の実践例である。本来はテストを書くたびに実行するが、ここでは省略している。
Step 1: 定義
まず、コンポーネントが <button> を描画することをテストする。
// CarouselButton.test.tsx
describe("CarouselButton", () => {
it("renders a <button>", () => {
render(<CarouselButton />)
expect(screen.getByRole("button")).toBeInTheDocument()
})
})テストをパスする最低限の実装は次のとおりである。
// CarouselButton.tsx
const CarouselButton = () => <button />
export default CarouselButtonStep 2: 子要素を Props として受け取る
children が <button> に渡されることをテストする。
// CarouselButton.test.tsx
it("passes `children` through to the <button>", () => {
const text = "Button text"
render(<CarouselButton>{text}</CarouselButton>)
expect(screen.getByRole("button")).toHaveTextContent(text)
})// CarouselButton.tsx
const CarouselButton = ({ children }: { children?: React.ReactNode }) => <button>{children}</button>Step 3: button 要素が扱うすべての Props を受け取る
className や data-action などの Props が、そのまま <button> に渡されることをテストする。
// CarouselButton.test.tsx
it("passes other props through to the <button>", () => {
const className = "my-carousel-button"
const dataAction = "prev"
render(
<CarouselButton className={className} data-action={dataAction}>
Button text
</CarouselButton>,
)
expect(screen.getByRole("button")).toHaveClass(className)
expect(screen.getByRole("button")).toHaveAttribute("data-action", dataAction)
})React の Props は基本的に camelCase で書くが、data- と aria- で始まるものは例外的に kebab-case で書く(React の公式ドキュメント)。
実装では ComponentPropsWithRef<"button"> を使い、受け取った Props をすべて <button> に渡す。
// CarouselButton.tsx
import { ComponentPropsWithRef } from "react"
const CarouselButton = (props: ComponentPropsWithRef<"button">) => <button {...props} />