はじめに
Inputフィールドに入力した後、「保存ボタン」や「送信ボタン」を押すことなく、内容保存できる機能の作成となると複数方法があると思います。
- Qiitaのように、記事の内容を1文字追加・削除する度にPOST Requestをサーバーサイドに投げる
- 入力が始まって、何秒か経過したらPOST Requestをサーバーサイドに投げる
- Inputフィールド以外の何かをクリックしたらPOST Requestをサーバーサイドに投げる
- 入力内容をInputタグからフォーカスを外した時に保存する
まだあるかもしれませんが、今回は4番目の内容についてまとめます。
テストで使用するメソッドはこちらです。
await fireEvent.blur(getByLabelText('username'))
完成図
- 「Blur Sample」のタイトルが
- inputフィールド
- 「Memo Record」のサブタイトル
- DBから引っ張ってきた文字の表示
全体構成
Backend側の実装も行なっています。
本筋と異なるので記載はしませんが、下記画像のような構成になっています。
テスト & 実装
TDDの勉強も兼ねて、テストと実装を書いたので内容は省略しますが、細かくテストを書いていきました。
Screen層の役割は、Repository層のpostMemoメソッドに正しい引数を渡すことです。
Test DoubleのSpyを使用してテスト用のRepositoryを作成します。
SpyRepository.ts
import { UserRepository } from "../../repository/UserRepository.ts";
import { UserMemo } from "../../type/TypeMemoRepository.ts";
export class SpyUserRepository implements UserRepository {
postMemo_argumentValue:UserMemo | null = null
postMemo(inputObject: UserMemo): void {
this.postMemo_argumentValue = inputObject
}
async getAllMemo(): Promise<UserMemo[]> {
// ...省略
}
}
次にScreen層(App.tsx)のテストです。
App.test.tsx
import {describe, expect, test} from "vitest"
import { screen, render, fireEvent } from "@testing-library/react"
import App from "../App.tsx";
import { MemoScreen } from "../screens/MemoScreen.tsx";
import { SpyUserRepository } from "./repository/SpyUserRepository.ts";
import { userEvent } from "@testing-library/user-event";
describe("Appのテスト", () => {
test('Appをレンダリングした時に「Blur Sample」の文字が見える', () => {
// ...省略
})
test('Appをレンダリングした時に、inputフィールドがある', () => {
// ...省略
})
test('Appをレンダリングした時に、MemoScreenコンポーネントをレンダリングする', () => {
// ...省略
})
test('メモを入力し、フォーカスを外した時にuserRepositoryのpostMemoメソッドを正しい引数で呼んでいる', async() => {
const spyUserRepository = new SpyUserRepository()
render(<App userRepository={spyUserRepository}/>)
const inputField = screen.getByRole('textbox')
await userEvent.type(inputField, 'Test Memo')
await fireEvent.blur(inputField)
expect(spyUserRepository.postMemo_argumentValue?.memo).toEqual('Test Memo')
})
})
そして、実装です。
App.tsx
import './App.css'
import { MemoScreen } from './screens/MemoScreen'
import { DefaultUserRepository, UserRepository } from "./repository/UserRepository.ts";
import { useEffect, useRef, useState } from "react";
import { UserMemo } from "./type/TypeMemoRepository.ts";
type Props = {
userRepository?: UserRepository
}
function App({ userRepository = new DefaultUserRepository() }: Props) {
const inputRef = useRef<HTMLInputElement>(null)
const [memo, setMemo] = useState<UserMemo[]>([])
const postMemo = async () => {
if (inputRef.current) {
const inputObject = {
memo : inputRef.current.value
}
await userRepository.postMemo(inputObject)
inputRef.current.value = ''
}
}
useEffect(() => {
const getAllMemo = async () => {
const res = await userRepository.getAllMemo()
setMemo(res)
}
getAllMemo()
}, [memo])
return (
<>
<h1>Blur Sample</h1>
<input
ref={inputRef}
onBlur={postMemo}
/>
<MemoScreen memo={memo}/>
</>
)
}
export default App
テスト側:await fireEvent.blur(<対象のelement>)
実装側:<input onBlur={<保存処理>} />
完成図のように、inputフィールドからフォーカスを外した時に、保存ができるようになりました。
最後に
すごく簡単はフルスタックアプリでしたが、学びが多かったです!
もっと作っていくことで、基本的なことを頭に叩き込んでいこうと思います!!