【Day 8】TOPページを作成する

## はじめに

![スライド9.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/6cf49a74-eabf-b8a5-d26c-cc5092c107b9.png)

---

**2023年アドベントカレンダー8日目**です。

https://qiita.com/advent-calendar/2023/life-sync-app

今回から、「ユーザーはトップページを開くことができる」を進めていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/5939e0e6-5866-bd23-a0ab-f8b27d84bb6c.png)

## Let's Go!!

以前作成したデザインによると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/0b3937fe-ffe4-946e-d78c-1c2ef05173b9.png)

このような画面のようです。

今回は「ユーザーはトップページを開くことができる」なので、ブログの一覧をDBから取得するような処理は描かず、赤枠の部分だけ作成することにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/308a75e7-0413-c08a-b4cf-90501424a346.png)

**TDD**(テスト駆動開発)で進めていきます

https://service.shiftinc.jp/column/4654/

ざっくりと今回の進め方を図解しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/f1304986-c723-653b-743c-a369319bdf2a.png)

## E2Eテスト

```cypress/e2e/top/display.cy.js
/// <reference types="cypress" />

describe('トップ画面の表示', () => {
  beforeEach(() => {
    cy.visit(cy.config('baseUrl'))
  })

  it('h1タグで"Life Sync"が表示されていること', () => {
    cy.get('h1').should('have.text', 'Life Sync')
  })

  it('h2タグで"ブログ一覧"の文字が表示されていること', () => {
    cy.get('h2').should('have.text', 'ブログ一覧')
  })

  it('"新規登作成"のボタンが表示されていること', () => {
    cy.get('button').should('have.text', '新規作成')
  })
})

```

```bash:web
npm run dev
```

```bash:e2e/web
npm run test:e2e
```

```bash
  トップ画面の表示
    1) h1タグで"Life Sync"が表示されていること
    2) h2タグで"ブログ一覧"の文字が表示されていること
    3) "新規登作成"のボタンが表示されていること


  0 passing
  3 failing
```

何も作成していないので、当然のようにテストは失敗します。

では次はUnit Testと行きたいところですが、今回のストーリーでは、ビジネスロジックを書くつもりはないので、E2Eテストのみで実装へ進みます。

## コンポーネント設計

作成したい画面をよく見て、コンポーネント設計をしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/3434b50c-fb98-082b-64ee-41e8d3ea1fec.png)

簡単にこのような形で分解しました。

:::note info
細かく分けるアトミックデザインのような考え方もありますが、自分は関心ごと単位で分けるくらいに抑えることが多いです。コンポーネントが増えると逆にメンテナンス性が下がることもありますので、、、

（参考）アトミックデザインとは
https://udemy.benesse.co.jp/design/web-design/atomic-design.html
:::

## 実装

今回のストーリーでは、`Header`, `PrimaryButton`を作っていきます。

**まずは最低限テストが通ることを目指します。**

```tsx:components/Header.tsx
import React from 'react'

export const Header = () => {
  return <h1>Life Sync</h1>
}
```

```tsx:components/Buttons/Primary.tsx
import React, { ButtonHTMLAttributes, FC, MouseEventHandler } from 'react'

interface Props {
  text: string
  type: ButtonHTMLAttributes<HTMLButtonElement>['type']
  onClick?: MouseEventHandler<HTMLButtonElement>
}

export const Primary: FC<Props> = ({ text, type, onClick }) => {
  const clickHander = onClick ? onClick : () => {}
  return <button type={type} onClick={clickHander}>{text}</button>
}
```

これをpage.tsxで呼び出してみます。

```tsx:page.tsx
'use client'

import { Blogs } from '@/components/Blogs'
import { Primary } from '@/components/Buttons/Primary'

export default function Home() {
  return (
    <main className='p-8 flex flex-col gap-4'>
      <div className='flex justify-between'>
        <h2 className='font-bold text-lg'>ブログ一覧</h2>
        <Primary text='新規作成' type='button' onClick={moveBlogNew} />
      </div>
    </main>
  )
}
```

さあテストを通してみましょう

```bash:e2e/web
npm run test:e2e
```

```bash
  トップ画面の表示
    ✓ h1タグで"Life Sync"が表示されていること (380ms)
    ✓ h2タグで"ブログ一覧"の文字が表示されていること (353ms)
    ✓ "新規登作成"のボタンが表示されていること (344ms)


  3 passing (1s)
```

:ok: 通りました！

次はスタイルをデザイン案を見ながら適用します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/6d26eff4-f362-036a-05aa-ffa73f5adce8.png)

再度E2Eテストを回します。

```bash:e2e/web
npm run test:e2e
```

```bash
  トップ画面の表示
    ✓ h1タグで"Life Sync"が表示されていること (380ms)
    ✓ h2タグで"ブログ一覧"の文字が表示されていること (353ms)
    ✓ "新規登作成"のボタンが表示されていること (344ms)


  3 passing (1s)
```

いい感じですね👍

まず最低限テストが通る実装をして、そのあとにリファクタリングをするようにしましょう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/e718faa1-3ddd-a7e2-da7d-f2a402d4e2f8.png)


### 完成コード

<details><summary>Header.tsx</summary>

```tsx:components/Header.tsx
import React from 'react'

export const Header = () => {
  return (
    <div className='w-full px-6 py-3 border-b-2 border-stone-800'>
      <h1 className='font-bold text-2xl'>Life Sync</h1>
    </div>
  )
}
```
</details>

<details><summary>Buttons/Primary.tsx</summary>

```tsx:components/Buttons/Primary.tsx
import React, { ButtonHTMLAttributes, FC, MouseEventHandler } from 'react'

interface Props {
  text: string
  type: ButtonHTMLAttributes<HTMLButtonElement>['type']
  onClick?: MouseEventHandler<HTMLButtonElement>
}

export const Primary: FC<Props> = ({ text, type, onClick }) => {
  const clickHander = onClick ? onClick : () => {}
  return (
    <button
      type={type}
      className='bg-green-500 py-2 px-4 rounded-lg w-fit text-white hover:bg-green-600'
      onClick={clickHander}
    >
      {text}
    </button>
  )
}
```
</details>