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 CarouselButton

Step 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} />

キーワード