diff --git a/.gitignore b/.gitignore index 9876f894..397eb575 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,8 @@ yarn-debug.log* yarn-error.log* /k8s/secret.yaml +/test-results/ +/playwright-report/ +/playwright/ /tests/tests-examples/ -/tests/tests-results/ -/tests/playwright-report/ -/playwright/.cache/ +/tests/logintest.ts \ No newline at end of file diff --git a/playwright.config.ts b/playwright.config.ts index 301801ee..fa8072fb 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,15 +1,15 @@ import { defineConfig, devices } from '@playwright/test'; +import path from 'path'; +require('dotenv').config(); -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// require('dotenv').config(); - +export const ADMINSTATE = path.join(__dirname, './playwright/.auth/admin.json'); +export const ALBASTATE = path.join(__dirname, './playwright/.auth/alba.json'); /** * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ + // globalSetup: require.resolve('./tests/globalSetup.ts'), + testDir: './tests', /* Run tests in files in parallel */ fullyParallel: true, @@ -24,39 +24,74 @@ export default defineConfig({ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://127.0.0.1:3000', + baseURL: `${process.env.REACT_APP_BASE_URL}`, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', }, - + timeout: 10000, /* Configure projects for major browsers */ projects: [ + // { + // name: 'setup', + // testMatch: /globalAdmin.setup\.ts/, + // use: { storageState: ADMINSTATE }, + // }, + // { + // name: 'setupAlba', + // testMatch: /globalAlba.setup\.ts/, + // use: { storageState: ALBASTATE }, + // }, { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + name: 'admin', + // dependencies: ['setup'], + use: { + ...devices['Desktop Chrome'], + storageState: ADMINSTATE, + }, + testMatch: '**/tests/admin/**', + testIgnore: '**/tests/alba/**', }, - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, + name: 'alba', + // dependencies: ['setupAlba'], + use: { + ...devices['Desktop Chrome'], + storageState: ALBASTATE, + }, + testMatch: '**/tests/alba/**', + testIgnore: '**/tests/admin/**', }, - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, + name: 'logout', + use: { ...devices['Desktop Chrome'] }, + testMatch: /login.spec\.ts/, }, + // { + // name: 'chromium', + // use: { ...devices['Desktop Chrome'] }, + // }, - /* Test against mobile viewports. */ // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, // }, + // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, // }, + /* Test against mobile viewports. */ + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, + /* Test against branded browsers. */ // { // name: 'Microsoft Edge', diff --git a/src/App.tsx b/src/App.tsx index 90b7b01e..67bbbc37 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,7 @@ -import { QueryCache, QueryClient, QueryClientProvider, useQueryErrorResetBoundary } from '@tanstack/react-query'; +import { QueryClientProvider, useQueryErrorResetBoundary } from '@tanstack/react-query'; import { convertPath } from 'apis/convertURI'; import ViewPortContainer from 'components/@commons/ViewPortContainer'; import ErrorFallback from 'error/ErrorFallback'; -import { defaultErrorHandler } from 'error/defaultErrorHandler'; import { Provider } from 'jotai'; import HomeIndex from 'pages/HomeIndex'; import KakaoAuthPage from 'pages/KakaoAuthPage'; @@ -19,27 +18,7 @@ import { ErrorBoundary } from 'react-error-boundary'; import { Route, Routes } from 'react-router-dom'; import { ThemeProvider } from 'styled-components'; import { myTheme } from 'styles/myTheme'; - -const queryClient = new QueryClient({ - queryCache: new QueryCache({ - onError(error, query) { - defaultErrorHandler(error || { name: 'unknownError' }); - setTimeout(() => { - queryClient.removeQueries(query.queryKey); - }, 1000); - }, - }), - defaultOptions: { - queries: { - useErrorBoundary: true, - retry: 0, - refetchOnWindowFocus: false, - }, - mutations: { - onError: (err) => defaultErrorHandler(err || { name: 'unknownError' }), - }, - }, -}); +import { queryClient } from 'utils/queryClient'; function App(): JSX.Element { const { reset } = useQueryErrorResetBoundary(); diff --git a/src/components/Calendar/CalenderOutter.tsx b/src/components/Calendar/CalenderOutter.tsx index 2f235e5a..b139acf1 100644 --- a/src/components/Calendar/CalenderOutter.tsx +++ b/src/components/Calendar/CalenderOutter.tsx @@ -1,59 +1,63 @@ import FlexContainer from 'components/@commons/FlexContainer'; import Text from 'components/@commons/Text'; import { NextButton, PrevButton } from 'components/@commons/icons/buttons'; -import { PrimitiveAtom, useAtom } from 'jotai'; -import React from 'react'; -import { WeekGrid } from './CalendarStyle'; import weekdayArray from 'utils/weekdayArray'; +import { WeekGrid } from './CalendarStyle'; -interface Props { - monthDataAtom: PrimitiveAtom<{ - year: number; - month: number; - }>; -} +const CalenderOutter = ({ selectedMonth, setMonth }: Props): JSX.Element => { + return ( + + + + + ); +}; -const CalenderOutter = ({ monthDataAtom }: Props): JSX.Element => { - const [nowMonth, setNowMonth] = useAtom(monthDataAtom); - const { year, month } = nowMonth; +export default CalenderOutter; +const MonthSelectBar = ({ selectedMonth, setMonth }: Props) => { + const { year, month } = selectedMonth; const monthMoveHandler = (dm: number) => { - const newDateObj = new Date(nowMonth.year, nowMonth.month + dm, 1); - const newObj = { year: newDateObj.getFullYear(), month: newDateObj.getMonth() }; - setNowMonth(newObj); + const newDateObj = new Date(year, month + dm, 1); + setMonth({ year: newDateObj.getFullYear(), month: newDateObj.getMonth() }); }; return ( - - - monthMoveHandler(-1)} /> + + monthMoveHandler(-1)} /> - - {`${year} 년`} - {`${month + 1} 월`} - - - monthMoveHandler(+1)} /> - - - + + {`${year} 년`} + {`${month + 1} 월`} + + monthMoveHandler(+1)} /> ); }; -export default CalenderOutter; - const DayTitle = () => { return ( - - {weekdayArray.map((e) => ( - - - {e.eng} - - - ))} - + + + {weekdayArray.map((e) => ( + + + {e.eng} + + + ))} + + ); }; + +export interface MonthData { + year: number; + month: number; +} + +interface Props { + selectedMonth: MonthData; + setMonth: (monthdata: MonthData) => void; +} diff --git a/src/components/Suspenses/DefferedSuspense.tsx b/src/components/Suspenses/DefferedSuspense.tsx index cbb51d42..01c4725a 100644 --- a/src/components/Suspenses/DefferedSuspense.tsx +++ b/src/components/Suspenses/DefferedSuspense.tsx @@ -1,12 +1,13 @@ -import React, { PropsWithChildren, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; -const DefferedSuspense = ({ children }: PropsWithChildren<{}>) => { +const DefferedSuspense = ({ children, deffered = true }: { children: React.ReactNode; deffered?: boolean }) => { const [isDeferred, setIsDeferred] = useState(false); + const time = deffered ? 200 : 0; useEffect(() => { const timeout = setTimeout(() => { setIsDeferred(true); - }, 200); + }, time); return () => clearTimeout(timeout); }, []); diff --git a/src/components/Suspenses/Loader.tsx b/src/components/Suspenses/Loader.tsx index fe6aba2d..00f5f3ba 100644 --- a/src/components/Suspenses/Loader.tsx +++ b/src/components/Suspenses/Loader.tsx @@ -1,16 +1,9 @@ -import React from 'react'; import { styled } from 'styled-components'; import DefferedSuspense from './DefferedSuspense'; -const StyledLoader = styled.div<{ $size?: string }>` - margin: auto; - width: ${(props) => (props.$size ? props.$size : '40px')}; - height: ${(props) => (props.$size ? props.$size : '40px')}; -`; - -const Loader = ({ size }: { size?: string }) => { +const Loader = ({ size, isDeffered = true }: { size?: string; isDeffered?: boolean }) => { return ( - + { }; export default Loader; + +const StyledLoader = styled.div<{ $size?: string }>` + margin: auto; + width: ${(props) => (props.$size ? props.$size : '40px')}; + height: ${(props) => (props.$size ? props.$size : '40px')}; +`; diff --git a/src/components/Suspenses/Skeleton.tsx b/src/components/Suspenses/Skeleton.tsx index 8292a89d..09edbf40 100644 --- a/src/components/Suspenses/Skeleton.tsx +++ b/src/components/Suspenses/Skeleton.tsx @@ -1,7 +1,23 @@ -import React from 'react'; import styled, { keyframes } from 'styled-components'; import DefferedSuspense from './DefferedSuspense'; +interface Props { + width?: string; + height?: string; + aspectRatio?: string; + isDeffered?: boolean; +} + +const Skeleton = ({ width, height, aspectRatio, isDeffered = true }: Props) => { + return ( + + + + ); +}; + +export default Skeleton; + const skeletonAnimation = keyframes` 0% { background-position: 200% 0; @@ -25,23 +41,3 @@ const SkeletonBox = styled.div<{ border-radius: 4px; object-fit: cover; `; - -const Skeleton = ({ width, height, aspectRatio, isDeffered }: Props) => { - if (isDeffered) { - return ( - - - - ); - } - return ; -}; - -export default Skeleton; - -interface Props { - width?: string; - height?: string; - aspectRatio?: string; - isDeffered?: boolean; -} diff --git a/src/components/modals/GetInviteKeyModal/index.tsx b/src/components/modals/GetInviteKeyModal/index.tsx index ad8244cb..1ce05808 100644 --- a/src/components/modals/GetInviteKeyModal/index.tsx +++ b/src/components/modals/GetInviteKeyModal/index.tsx @@ -1,17 +1,22 @@ import { useQuery } from '@tanstack/react-query'; import { getInviteKey } from 'apis/admin/manageGroup'; - import FlexContainer from 'components/@commons/FlexContainer'; import SubmitButton from 'components/@commons/SubmitButton'; import Text from 'components/@commons/Text'; +import { CheckIcon } from 'components/@commons/icons'; +import Loader from 'components/Suspenses/Loader'; +import { LinkBox } from 'components/modals/GetInviteKeyModal/styles'; import useModal from 'hooks/useModal'; -import React from 'react'; +import { useState } from 'react'; import CopyToClipboard from 'react-copy-to-clipboard'; -import styled from 'styled-components'; +import { myTheme } from 'styles/myTheme'; const GetInviteKeyModal = (): JSX.Element => { - const { data: inviteKeyData } = useQuery(['inviteKey'], getInviteKey); + const { data: inviteKeyData, isFetching } = useQuery(['inviteKey'], getInviteKey); const { modalOffHandler } = useModal(); + const [isCopied, setIsCopied] = useState(false); + const link = `${process.env.REACT_APP_BASE_URL}/invited/${inviteKeyData?.data.invitationKey}`; + return ( @@ -20,31 +25,33 @@ const GetInviteKeyModal = (): JSX.Element => { 아래 링크에 접속하면 그룹에 가입됩니다. - - {inviteKeyData?.data.invitationKey} - + + {isFetching ? : } + - - 복사하기 - - + {isFetching && ( + + + + )} + {!isFetching && ( + + {!isCopied ? ( + setIsCopied(true)}>복사하기 + ) : ( + + 복사됨 + + + )} + + )} + modalOffHandler()}> + 닫기 + ); }; export default GetInviteKeyModal; - -const Box = styled.div` - display: flex; - justify-content: center; - padding: 10px; - border: 1px solid; - border-color: ${({ theme }) => theme.color.gray}; -`; - -const Button = styled(SubmitButton)` - background-color: ${({ theme }) => theme.color.backgroundColor}; - border: 2px solid; - border-color: ${({ theme }) => theme.color.yellow}; -`; diff --git a/src/components/modals/GetInviteKeyModal/styles.tsx b/src/components/modals/GetInviteKeyModal/styles.tsx new file mode 100644 index 00000000..c607f119 --- /dev/null +++ b/src/components/modals/GetInviteKeyModal/styles.tsx @@ -0,0 +1,13 @@ +import styled from 'styled-components'; + +export const LinkBox = styled.input` + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + border: 1px solid; + border-color: ${({ theme }) => theme.color.gray}; + overflow-x: scroll; + text-align: center; + height: 100%; +`; diff --git a/src/error/defaultErrorHandler.ts b/src/error/defaultErrorHandler.ts index 2dfa91e8..d32f9f0a 100644 --- a/src/error/defaultErrorHandler.ts +++ b/src/error/defaultErrorHandler.ts @@ -11,6 +11,7 @@ export const defaultErrorHandler = (error: ErrorData) => { if (error.response === undefined) { alert('서버 오류'); + removeLoginData(); return; } @@ -71,6 +72,7 @@ export const defaultErrorHandler = (error: ErrorData) => { default: alert(`잘못된 접근입니다`); + removeLoginData(); redirect(convertPath('/')); return; } diff --git a/src/pages/SchedulePage/index.tsx b/src/pages/SchedulePage/index.tsx index 01d89c3a..a8770c9f 100644 --- a/src/pages/SchedulePage/index.tsx +++ b/src/pages/SchedulePage/index.tsx @@ -1,19 +1,17 @@ -import React, { Suspense } from 'react'; -import { useAtomValue } from 'jotai'; -import { memberAtom, monthAtom } from './states'; - -import PageContainer from 'components/@commons/PageContainer'; +import { UserData } from 'apis/types'; import FlexContainer from 'components/@commons/FlexContainer'; -import CalenderOutter from 'components/Calendar/CalenderOutter'; - +import PageContainer from 'components/@commons/PageContainer'; +import CalenderOutter, { MonthData } from 'components/Calendar/CalenderOutter'; +import Loader from 'components/Suspenses/Loader'; +import Skeleton from 'components/Suspenses/Skeleton'; +import { useAtom, useAtomValue } from 'jotai'; import CalenderConents from 'pages/SchedulePage/CalendarSection/CalenderConents'; import DailyWorkers from 'pages/SchedulePage/DailyWorkerSection/DailyWorkers'; import Dropdown from 'pages/SchedulePage/HeaderSection/Dropdown'; import TotalWorkTime from 'pages/SchedulePage/HeaderSection/TotalWorkTime'; -import { UserData } from 'apis/types'; -import Loader from 'components/Suspenses/Loader'; -import Skeleton from 'components/Suspenses/Skeleton'; +import { Suspense } from 'react'; import { getLoginData } from 'utils/loginDatahandlers'; +import { memberAtom, monthAtom } from './states'; const SchedulePage = ({ members }: { members?: UserData[] }): JSX.Element => { const isAdmin = getLoginData().isAdmin; @@ -32,7 +30,7 @@ const SchedulePage = ({ members }: { members?: UserData[] }): JSX.Element => { {nowMember.isSelected && ( - + }> @@ -49,3 +47,11 @@ const SchedulePage = ({ members }: { members?: UserData[] }): JSX.Element => { }; export default SchedulePage; + +const MonthSelector = () => { + const [selectedMonth, setter] = useAtom(monthAtom); + const setMonth = (newMonth: MonthData) => { + setter(newMonth); + }; + return ; +}; diff --git a/src/pages/SelectWeekPage/index.tsx b/src/pages/SelectWeekPage/index.tsx index d6171956..89008aa5 100644 --- a/src/pages/SelectWeekPage/index.tsx +++ b/src/pages/SelectWeekPage/index.tsx @@ -1,7 +1,8 @@ import FlexContainer from 'components/@commons/FlexContainer'; import PageContainer from 'components/@commons/PageContainer'; -import CalenderOutter from 'components/Calendar/CalenderOutter'; +import CalenderOutter, { MonthData } from 'components/Calendar/CalenderOutter'; import Skeleton from 'components/Suspenses/Skeleton'; +import { useAtom } from 'jotai'; import AdminDetailSect from 'pages/SelectWeekPage/AdminDetailSection'; import AlbaSubmitButton from 'pages/SelectWeekPage/AlbaSubmitButton'; import StatusCalendar from 'pages/SelectWeekPage/Calendar/StatusCalendar'; @@ -12,10 +13,9 @@ const SelectWeekPage = ({ isAdmin }: { isAdmin: boolean }): JSX.Element => { return ( - + }> - {isAdmin ? : } @@ -26,3 +26,11 @@ const SelectWeekPage = ({ isAdmin }: { isAdmin: boolean }): JSX.Element => { }; export default SelectWeekPage; + +const MonthSelector = () => { + const [selectedMonth, setter] = useAtom(weekStatusMonthAtom); + const setMonth = (newMonth: MonthData) => { + setter(newMonth); + }; + return ; +}; diff --git a/src/utils/queryClient.ts b/src/utils/queryClient.ts new file mode 100644 index 00000000..db4044c8 --- /dev/null +++ b/src/utils/queryClient.ts @@ -0,0 +1,23 @@ +import { QueryCache, QueryClient } from '@tanstack/query-core'; +import { defaultErrorHandler } from 'error/defaultErrorHandler'; + +export const queryClient = new QueryClient({ + queryCache: new QueryCache({ + onError(error, query) { + defaultErrorHandler(error || { name: 'unknownError' }); + setTimeout(() => { + queryClient.removeQueries(query.queryKey); + }, 1000); + }, + }), + defaultOptions: { + queries: { + useErrorBoundary: true, + retry: 0, + refetchOnWindowFocus: false, + }, + mutations: { + onError: (err) => defaultErrorHandler(err || { name: 'unknownError' }), + }, + }, +}); diff --git a/tests/admin/addGroup.spec.ts b/tests/admin/addGroup.spec.ts new file mode 100644 index 00000000..16012010 --- /dev/null +++ b/tests/admin/addGroup.spec.ts @@ -0,0 +1,64 @@ +import { expect, test } from '@playwright/test'; +import { getMyinfoNoGroup } from '../mock/getMyInfo'; +import { mockResponse } from '../mock/mockResponse'; + +test.beforeEach(async ({ page }) => { + await page.route('*/**/group', async (route) => { + if (route.request().method() === 'GET') { + await route.fulfill(mockResponse(getMyinfoNoGroup)); + } else { + await route.fulfill(mockResponse(null)); + } + }); +}); + +test.describe('그룹 생성 페이지', () => { + test('그룹 생성', async ({ page, baseURL }) => { + await page.goto(`${baseURL}/addGroup`); + expect(page.getByText('매장 등록하기')).toBeVisible(); + + const marketName = page.getByLabel('상호명'); + const marketNumber = page.getByLabel('사업자 번호'); + const address = page.getByLabel('상세 주소'); + const mainAddress = page.getByLabel('주소', { exact: true }); + + await marketName.focus(); + await marketName.fill('카카오 프렌즈샵'); + + await marketNumber.focus(); + await marketNumber.fill('1111111111'); + + await address.focus(); + await address.fill('11'); + + await mainAddress.click(); + await page + .frameLocator('iframe[title="우편번호서비스 레이어 프레임"]') + .frameLocator('iframe[title="우편번호 검색 프레임"]') + .getByText('예) 판교역로 166, 분당 주공, 백현동 532') + .click(); + await page + .frameLocator('iframe[title="우편번호서비스 레이어 프레임"]') + .frameLocator('iframe[title="우편번호 검색 프레임"]') + .getByLabel('검색할 도로명/지번주소를 입력, 예시) 판교역로 166, 분당 주공, 백현동 532') + .fill('성동구 서울숲길'); + await page + .frameLocator('iframe[title="우편번호서비스 레이어 프레임"]') + .frameLocator('iframe[title="우편번호 검색 프레임"]') + .getByRole('link', { name: '서울특별시 성동구 서울숲길' }) + .click(); + await page + .frameLocator('iframe[title="우편번호서비스 레이어 프레임"]') + .frameLocator('iframe[title="우편번호 검색 프레임"]') + .getByRole('button', { name: '서울 성동구 서울숲길 17 (성수파크빌)' }) + .click(); + + await page.waitForTimeout(2000); + + await page.getByRole('button', { name: '그룹 생성하기' }).click(); + + await page.getByText('매장 등록에 성공했습니다').isVisible({ timeout: 10000 }); + + expect(page.getByText('매장 등록에 성공했습니다')).toBeVisible({ timeout: 10000 }); + }); +}); diff --git a/tests/admin/sidebar.spec.ts b/tests/admin/sidebar.spec.ts new file mode 100644 index 00000000..88e60018 --- /dev/null +++ b/tests/admin/sidebar.spec.ts @@ -0,0 +1,25 @@ +import { expect, test } from '@playwright/test'; +import { getMyinfo, getMyinfoNoGroup } from '../mock/getMyInfo'; +import { mockMapper, mockResponse } from '../mock/mockResponse'; + +test.describe('사이드바', () => { + test('사이드바 : 그룹 있음', async ({ page, baseURL }) => { + await mockMapper({ page: page, url: 'group', method: 'GET', response: mockResponse(getMyinfo) }); + + await page.goto(`${baseURL}`); + await page.getByLabel('메뉴').click(); + await page.getByText('우리 매장 직원 목록').isVisible(); + + expect(page.getByText('우리 매장 직원 목록')).toBeVisible(); + }); + + test('사이드바 : 그룹 없음', async ({ page, baseURL }) => { + await mockMapper({ page: page, url: 'group', method: 'GET', response: mockResponse(getMyinfoNoGroup) }); + + await page.goto(`${baseURL}`); + await page.getByLabel('메뉴').click(); + await page.waitForTimeout(3000); + + expect(page.getByText('우리 매장 직원 목록')).not.toBeVisible(); + }); +}); diff --git a/tests/alba/invitation.spec.ts b/tests/alba/invitation.spec.ts new file mode 100644 index 00000000..555810ba --- /dev/null +++ b/tests/alba/invitation.spec.ts @@ -0,0 +1,30 @@ +import { expect, test } from '@playwright/test'; +import { getGroupInfo } from '../mock/getInvitation'; +import { mockMapper, mockResponse } from '../mock/mockResponse'; + +test.describe('초대장', () => { + test('접속', async ({ page, baseURL }) => { + await mockMapper({ + page: page, + url: 'group/invitation/information*', + method: 'GET', + response: mockResponse(getGroupInfo), + }); + await mockMapper({ + page: page, + url: 'group/invitation', + method: 'POST', + response: mockResponse(null), + }); + + // 접속 + await page.goto(`${baseURL}/invited/123`); + const marketName = page.getByText('라이언 월드'); + expect(marketName).toBeVisible(); + + // 승인 + await page.getByRole('button', { name: '승인하기' }).click(); + const successMessage = page.getByText('그룹 가입에 성공했어요'); + expect(successMessage).toBeVisible(); + }); +}); diff --git a/tests/mock/auth.ts b/tests/mock/auth.ts new file mode 100644 index 00000000..5de9a1fe --- /dev/null +++ b/tests/mock/auth.ts @@ -0,0 +1,15 @@ +export const postLoginNoUser = { + status: 404, + body: JSON.stringify({ + code: -10006, + }), +}; + +export const postLoginAdmin = { + token: 'Bearer ABC', + isAdmin: true, +}; +export const postLoginAlba = { + token: 'Bearer ABC', + isAdmin: false, +}; diff --git a/tests/mock/getInvitation.ts b/tests/mock/getInvitation.ts new file mode 100644 index 00000000..6a8d45fe --- /dev/null +++ b/tests/mock/getInvitation.ts @@ -0,0 +1,3 @@ +export const getGroupInfo = { + marketName: '라이언 월드', +}; diff --git a/tests/mock/getMyInfo.ts b/tests/mock/getMyInfo.ts new file mode 100644 index 00000000..de8ada83 --- /dev/null +++ b/tests/mock/getMyInfo.ts @@ -0,0 +1,31 @@ +export const getMyinfoNoGroup = { + userName: '라이언', + groupName: null, + members: [], +}; + +export const getMyinfo = { + userName: '라이언', + groupName: '라이언 월드', + members: [ + { name: '라이언', userId: 1 }, + { name: '라이언', userId: 2 }, + { name: '라이언', userId: 3 }, + { name: '라이언', userId: 4 }, + { name: '라이언', userId: 5 }, + { name: '라이언', userId: 6 }, + { name: '라이언', userId: 7 }, + { name: '라이언', userId: 8 }, + { name: '라이언', userId: 9 }, + { name: '라이언', userId: 10 }, + { name: '라이언', userId: 11 }, + { name: '라이언', userId: 12 }, + { name: '라이언', userId: 13 }, + { name: '라이언', userId: 14 }, + { name: '라이언', userId: 15 }, + { name: '라이언', userId: 16 }, + { name: '라이언', userId: 17 }, + { name: '라이언', userId: 18 }, + { name: '라이언', userId: 19 }, + ], +}; diff --git a/tests/mock/mockResponse.ts b/tests/mock/mockResponse.ts new file mode 100644 index 00000000..49a6d22f --- /dev/null +++ b/tests/mock/mockResponse.ts @@ -0,0 +1,33 @@ +import { Page } from 'playwright-core'; + +export const mockResponse = (responseBody) => { + if (responseBody === null) { + return { status: 200 }; + } + return { status: 200, body: JSON.stringify(responseBody) }; +}; + +export const mockMapper = async ({ + page, + response, + url, + method, +}: { + page: Page; + response: Response; + url: string; + method: 'GET' | 'POST' | 'PUT' | 'DELETE'; +}) => { + await page.route(`*/**/${url}`, async (route) => { + if (route.request().method() === method) { + await route.fulfill(response); + } else { + await route.continue(); + } + }); +}; + +interface Response { + body?: string | Buffer | undefined; + status?: number | undefined; +}