From e0b9bf42d41f25b4659c8f872e104a751878940e Mon Sep 17 00:00:00 2001 From: shunsei Date: Wed, 30 Oct 2024 17:32:34 +0900 Subject: [PATCH] Write login page test (#102) --- frontend/test/helpers/wrapper.tsx | 30 +++--- frontend/test/mocks/@remix-run/cloudflare.ts | 5 + frontend/test/routes/auth.login.test.tsx | 99 +++++++++++++++++++- 3 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 frontend/test/mocks/@remix-run/cloudflare.ts diff --git a/frontend/test/helpers/wrapper.tsx b/frontend/test/helpers/wrapper.tsx index 7d234ecd..1cdc4489 100644 --- a/frontend/test/helpers/wrapper.tsx +++ b/frontend/test/helpers/wrapper.tsx @@ -1,11 +1,11 @@ import { MantineProvider } from '@mantine/core'; import { Notifications } from '@mantine/notifications'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { render } from '@testing-library/react'; +import { render, RenderOptions } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { ReactElement } from 'react'; +import { ReactElement, ReactNode } from 'react'; -export const renderWithWrapper = (children: ReactElement) => { +const wrappper = ({ children }: { children: ReactNode }) => { const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -15,15 +15,23 @@ export const renderWithWrapper = (children: ReactElement) => { }, }, }); + + return ( + + + + {children} + + + ); +}; + +export const customRender = ( + ui: ReactElement, + options?: Omit, +) => { return { user: userEvent.setup(), - ...render( - - - - {children} - - , - ), + ...render(ui, { wrapper: wrappper, ...options }), }; }; diff --git a/frontend/test/mocks/@remix-run/cloudflare.ts b/frontend/test/mocks/@remix-run/cloudflare.ts new file mode 100644 index 00000000..559d2a15 --- /dev/null +++ b/frontend/test/mocks/@remix-run/cloudflare.ts @@ -0,0 +1,5 @@ +export const redirect = vi + .fn() + .mockImplementation((url: string, init?: number | ResponseInit) => { + return null; + }); diff --git a/frontend/test/routes/auth.login.test.tsx b/frontend/test/routes/auth.login.test.tsx index f32b365b..71e1e3ff 100644 --- a/frontend/test/routes/auth.login.test.tsx +++ b/frontend/test/routes/auth.login.test.tsx @@ -1,26 +1,123 @@ +import type * as remixruncloudflare from '@remix-run/cloudflare'; +import { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/cloudflare'; +import { createRemixStub } from '@remix-run/testing'; +import { screen } from '@testing-library/react'; +import LoginPage, { action, loader } from '~/routes/auth.login'; +import { customRender } from '../helpers/wrapper'; +import { redirect } from '../mocks/@remix-run/cloudflare'; + +vi.mock('@remix-run/cloudflare', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + redirect: (url: string, init?: number | ResponseInit) => { + return redirect(url, init); + }, + }; +}); + +const LoginPageStub = createRemixStub([ + { + path: '/login', + Component: LoginPage, + async loader({ request }) { + return await loader({ request } as LoaderFunctionArgs); + }, + async action({ request }) { + return await action({ request } as ActionFunctionArgs); + }, + }, +]); + describe('Login page', () => { it('should login successfully', async () => { + const { user } = customRender( + , + ); + // メールアドレスを入力 + const emailForm = await screen.findByLabelText('メールアドレス'); + await user.type(emailForm, 'user@example.com'); + expect(emailForm).toHaveValue('user@example.com'); + // パスワードを入力 + const passwordForm = await screen.findByLabelText('パスワード'); + await user.type(passwordForm, 'passw0rd'); + expect(passwordForm).toHaveValue('passw0rd'); + // ログインボタンをクリック - // ログイン成功の通知が表示される + // prettier-ignore + const submitButton = await screen.findByRole('button', { name: 'ログイン' }); + await user.click(submitButton); + + // マイページへリダイレクトされる + // prettier-ignore + expect(redirect).toHaveBeenCalledWith( + '/home/mypage', + { + headers: { + 'Set-Cookie': expect.any(String), + }, + } + ); }); it('should fail to pass when email is invalid', async () => { + const { user } = customRender( + , + ); + // メールアドレスを入力 + const emailForm = await screen.findByLabelText('メールアドレス'); + await user.type(emailForm, 'user@invalid'); + // ログインボタンをクリック + // prettier-ignore + const submitButton = await screen.findByRole('button', { name: 'ログイン' }); + await user.click(submitButton); + // エラーメッセージが表示される + const message = await screen.findByText('有効でないメールアドレスです'); + expect(message).toBeInTheDocument(); }); it('should fail to pass when password length is less than 8', async () => { + const { user } = customRender( + , + ); + // パスワードを入力 + const passwordForm = await screen.findByLabelText('パスワード'); + await user.type(passwordForm, 'hoge'); + // ログインボタンをクリック + // prettier-ignore + const submitButton = await screen.findByRole('button', { name: 'ログイン' }); + await user.click(submitButton); + // エラーメッセージが表示される + // prettier-ignore + const message = await screen.findByText('パスワードは8文字以上で入力してください'); + expect(message).toBeInTheDocument(); }); it('should fail to pass when password is not alphanumeric', async () => { + const { user } = customRender( + , + ); + // パスワードを入力 + const passwordForm = await screen.findByLabelText('パスワード'); + await user.type(passwordForm, 'password'); + // ログインボタンをクリック + // prettier-ignore + const submitButton = await screen.findByRole('button', { name: 'ログイン' }); + await user.click(submitButton); + // エラーメッセージが表示される + // prettier-ignore + const message = await screen.findByText('パスワードにはアルファベットと数字を含めてください'); + expect(message).toBeInTheDocument(); }); });