From f9bae8593524b977cae019b39ef94e3683ec24d8 Mon Sep 17 00:00:00 2001 From: ctaaag Date: Wed, 31 May 2023 21:30:31 +0900 Subject: [PATCH 01/17] ADD: RegionContainer test complete --- src/App.jsx | 11 ++++++++++- src/CategoriesContainer.jsx | 5 +++++ src/RegionContainer.jsx | 10 ++++++++++ src/RegionContainer.test.jsx | 33 +++++++++++++++++++++++++++++++++ src/RestaurantsContainer.jsx | 5 +++++ 5 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/CategoriesContainer.jsx create mode 100644 src/RegionContainer.jsx create mode 100644 src/RegionContainer.test.jsx create mode 100644 src/RestaurantsContainer.jsx diff --git a/src/App.jsx b/src/App.jsx index 0bd563929..294b81bdd 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,14 @@ +import RegionContainer from './RegionContainer'; +import CategoriesContainer from './CategoriesContainer'; +import RestaurantsContainer from './RestaurantsContainer'; + export default function App() { return ( - <> + <> +

Restaurants

+ + + + ); } diff --git a/src/CategoriesContainer.jsx b/src/CategoriesContainer.jsx new file mode 100644 index 000000000..c9d787e77 --- /dev/null +++ b/src/CategoriesContainer.jsx @@ -0,0 +1,5 @@ +export default function CategoriesContainer() { + return ( +
CategoriesContainer
+ ); +} diff --git a/src/RegionContainer.jsx b/src/RegionContainer.jsx new file mode 100644 index 000000000..2be637d7f --- /dev/null +++ b/src/RegionContainer.jsx @@ -0,0 +1,10 @@ +export default function RegionContainer() { + function handleClickButton() { + console.log('hi'); + return []; + } + + return ( + + ); +} diff --git a/src/RegionContainer.test.jsx b/src/RegionContainer.test.jsx new file mode 100644 index 000000000..b0b719ecd --- /dev/null +++ b/src/RegionContainer.test.jsx @@ -0,0 +1,33 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import RegionContainer from './RegionContainer'; + +describe('RegionContainer', () => { + // TODO + // 1. API를 받아서 데이터 받아오기 + // 2. 하위 컴포넌트에 Region data 넘겨주기 + // 3. 지역 선택시 state에 넘겨주기 + + const renderRegionContainer = () => render(); + + it('데이터 받기', () => { + const { getByText } = renderRegionContainer(); + const handleClickButton = jest.fn(); + // 데이터 페칭하기 + const regions = [ + { id: 1, name: '서울' }, + ]; + // fireEvent.change(getByLabelText('할 일'), { + // target: { value: '무언가 하기' }, + // }); + + // expect(handleChange).toBeCalled(); + + // fireEvent.click(getByText('추가')); + + // expect(handleClick).toBeCalled(); + // 페칭한 데이터 있는지 확인 + expect(getByText('서울')).not.toBeNull(); + fireEvent.click(getByText('서울')); + expect(handleClickButton).toHaveBeenCalled(); + }); +}); diff --git a/src/RestaurantsContainer.jsx b/src/RestaurantsContainer.jsx new file mode 100644 index 000000000..e2b7ff640 --- /dev/null +++ b/src/RestaurantsContainer.jsx @@ -0,0 +1,5 @@ +export default function RestaurantsContainer() { + return ( +
RestaurantsContainer
+ ); +} From bb99cf9fea6e5d28da4dd3c9744da233081ffe25 Mon Sep 17 00:00:00 2001 From: ctaaag Date: Wed, 31 May 2023 21:47:58 +0900 Subject: [PATCH 02/17] =?UTF-8?q?ADD:=20redux=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __mocks__/react-redux.js | 3 +++ src/RegionContainer.test.jsx | 9 --------- src/index.jsx | 6 +++++- src/reducer.js | 12 ++++++++++++ src/store.js | 3 +++ 5 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 __mocks__/react-redux.js create mode 100644 src/reducer.js create mode 100644 src/store.js diff --git a/__mocks__/react-redux.js b/__mocks__/react-redux.js new file mode 100644 index 000000000..6a6ea8eba --- /dev/null +++ b/__mocks__/react-redux.js @@ -0,0 +1,3 @@ +export const useDispatch = jest.fn(); + +export const useSelector = jest.fn((selector) => selector({})); diff --git a/src/RegionContainer.test.jsx b/src/RegionContainer.test.jsx index b0b719ecd..55aca2e97 100644 --- a/src/RegionContainer.test.jsx +++ b/src/RegionContainer.test.jsx @@ -16,15 +16,6 @@ describe('RegionContainer', () => { const regions = [ { id: 1, name: '서울' }, ]; - // fireEvent.change(getByLabelText('할 일'), { - // target: { value: '무언가 하기' }, - // }); - - // expect(handleChange).toBeCalled(); - - // fireEvent.click(getByText('추가')); - - // expect(handleClick).toBeCalled(); // 페칭한 데이터 있는지 확인 expect(getByText('서울')).not.toBeNull(); fireEvent.click(getByText('서울')); diff --git a/src/index.jsx b/src/index.jsx index 5752f9375..5be1b8c93 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,8 +1,12 @@ import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; import App from './App'; +import store from './store'; ReactDOM.render( - , + + + , document.getElementById('app'), ); diff --git a/src/reducer.js b/src/reducer.js new file mode 100644 index 000000000..d72b29e67 --- /dev/null +++ b/src/reducer.js @@ -0,0 +1,12 @@ +const initialState = () => { + +}; + +const handleAction = () => { + +}; + +export default store = (state, action) => { + console.log(state); + return state; +}; diff --git a/src/store.js b/src/store.js new file mode 100644 index 000000000..2595edd71 --- /dev/null +++ b/src/store.js @@ -0,0 +1,3 @@ +import { createStore } from 'redux'; + +export default store = () => createStore(); From a87f515ca5d48a26510b6f6ec08c59afd9e01d45 Mon Sep 17 00:00:00 2001 From: ctaaag Date: Wed, 31 May 2023 21:55:00 +0900 Subject: [PATCH 03/17] =?UTF-8?q?Refactor:RegionContainer=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/RegionContainer.jsx | 3 +-- src/RegionContainer.test.jsx | 16 +++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/RegionContainer.jsx b/src/RegionContainer.jsx index 2be637d7f..27848803c 100644 --- a/src/RegionContainer.jsx +++ b/src/RegionContainer.jsx @@ -1,7 +1,6 @@ export default function RegionContainer() { function handleClickButton() { - console.log('hi'); - return []; + } return ( diff --git a/src/RegionContainer.test.jsx b/src/RegionContainer.test.jsx index 55aca2e97..7c63b310e 100644 --- a/src/RegionContainer.test.jsx +++ b/src/RegionContainer.test.jsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { useDispatch, useSelector } from 'react-redux'; import RegionContainer from './RegionContainer'; describe('RegionContainer', () => { @@ -6,19 +7,24 @@ describe('RegionContainer', () => { // 1. API를 받아서 데이터 받아오기 // 2. 하위 컴포넌트에 Region data 넘겨주기 // 3. 지역 선택시 state에 넘겨주기 + // 4. 리덕스 구조 짜기 + + // mocks로 모킹하기 const renderRegionContainer = () => render(); it('데이터 받기', () => { const { getByText } = renderRegionContainer(); - const handleClickButton = jest.fn(); - // 데이터 페칭하기 - const regions = [ + const data = useSelector.mockImplementation(() => [ { id: 1, name: '서울' }, - ]; + ]); + const dispatch = jest.fn(); + useDispatch.mockImplementation(() => dispatch); + + // 데이터 페칭하기 // 페칭한 데이터 있는지 확인 expect(getByText('서울')).not.toBeNull(); fireEvent.click(getByText('서울')); - expect(handleClickButton).toHaveBeenCalled(); + expect(dispatch).toBeCalled(); }); }); From edf24637844dd6f45405fc3dae2ef74bcc38419b Mon Sep 17 00:00:00 2001 From: ctaaag Date: Wed, 31 May 2023 23:22:35 +0900 Subject: [PATCH 04/17] =?UTF-8?q?ADD:Region=EC=BB=A8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=84=88=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/RegionContainer.jsx | 26 ++++++++++++++++++++++++-- src/RegionContainer.test.jsx | 11 +++++------ src/action.js | 15 +++++++++++++++ src/index.jsx | 1 - src/reducer.js | 24 ++++++++++++++++-------- src/services/__mock__/fetchData.js | 3 +++ src/services/fetchData.js | 10 ++++++++++ src/store.js | 8 ++++++-- 8 files changed, 79 insertions(+), 19 deletions(-) create mode 100644 src/action.js create mode 100644 src/services/__mock__/fetchData.js create mode 100644 src/services/fetchData.js diff --git a/src/RegionContainer.jsx b/src/RegionContainer.jsx index 27848803c..6eae15132 100644 --- a/src/RegionContainer.jsx +++ b/src/RegionContainer.jsx @@ -1,9 +1,31 @@ +import { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { fetchRegion } from './action'; + export default function RegionContainer() { - function handleClickButton() { + // 데이터 페칭하기 + const dispatch = useDispatch(); + useEffect(() => { + dispatch(fetchRegion()); + }, []); + const { region } = useSelector((state) => state); + function handleClickButton(item) { + console.log(item); } return ( - +
+ {region?.regionData.map((item) => ( + + ))} +
+ ); } diff --git a/src/RegionContainer.test.jsx b/src/RegionContainer.test.jsx index 7c63b310e..dc3505102 100644 --- a/src/RegionContainer.test.jsx +++ b/src/RegionContainer.test.jsx @@ -7,19 +7,18 @@ describe('RegionContainer', () => { // 1. API를 받아서 데이터 받아오기 // 2. 하위 컴포넌트에 Region data 넘겨주기 // 3. 지역 선택시 state에 넘겨주기 - // 4. 리덕스 구조 짜기 - - // mocks로 모킹하기 const renderRegionContainer = () => render(); - + jest.mock('react-redux'); + jest.mock('./services/fetchData'); it('데이터 받기', () => { const { getByText } = renderRegionContainer(); - const data = useSelector.mockImplementation(() => [ + useSelector.mockImplementation(() => [ { id: 1, name: '서울' }, + { id: 2, name: '대전' }, ]); const dispatch = jest.fn(); - useDispatch.mockImplementation(() => dispatch); + useDispatch.mockImplementation(() => dispatch()); // 데이터 페칭하기 // 페칭한 데이터 있는지 확인 diff --git a/src/action.js b/src/action.js new file mode 100644 index 000000000..1945da025 --- /dev/null +++ b/src/action.js @@ -0,0 +1,15 @@ +import { regionData } from './services/fetchData'; + +export function setRegionData(data) { + return { + type: 'SET_REGION_DATA', + payload: { regionData: data }, + }; +} + +export function fetchRegion() { + return (async (dispatch) => { + const data = await regionData(); + await dispatch(setRegionData(data)); + }); +} diff --git a/src/index.jsx b/src/index.jsx index 5be1b8c93..fde9ee3e7 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,6 +1,5 @@ import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; - import App from './App'; import store from './store'; diff --git a/src/reducer.js b/src/reducer.js index d72b29e67..78fd1a9dc 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -1,12 +1,20 @@ -const initialState = () => { +const initialState = { regionData: '' }; +const handleAction = { + SET_REGION_DATA: (state, action) => { + const { regionData } = action.payload; + console.log(regionData); + return { + ...state, + region: regionData, + }; + }, }; -const handleAction = () => { - -}; - -export default store = (state, action) => { - console.log(state); +export default function store(state = initialState, action) { + if (handleAction[action.type]) { + console.log(action.type); + return handleAction[action.type](state, action); + } return state; -}; +} diff --git a/src/services/__mock__/fetchData.js b/src/services/__mock__/fetchData.js new file mode 100644 index 000000000..0425fb43e --- /dev/null +++ b/src/services/__mock__/fetchData.js @@ -0,0 +1,3 @@ +export async function regionData() { + return []; +} diff --git a/src/services/fetchData.js b/src/services/fetchData.js new file mode 100644 index 000000000..75bcb86cf --- /dev/null +++ b/src/services/fetchData.js @@ -0,0 +1,10 @@ +export async function regionData() { + const url = 'https://eatgo-customer-api.ahastudio.com/regions'; + try { + const data = await fetch(url); + const result = { regionData: await data.json() }; + return result; + } catch (error) { + alert(error); + } +} diff --git a/src/store.js b/src/store.js index 2595edd71..7a6984f8f 100644 --- a/src/store.js +++ b/src/store.js @@ -1,3 +1,7 @@ -import { createStore } from 'redux'; +import { createStore, applyMiddleware } from 'redux'; +import thunk from 'redux-thunk'; +import reducer from './reducer'; -export default store = () => createStore(); +const store = createStore(reducer, applyMiddleware(thunk)); + +export default store; From c22d949e2b1b3a861a888f4f9273b21fb4084870 Mon Sep 17 00:00:00 2001 From: ctaaag Date: Thu, 1 Jun 2023 16:57:50 +0900 Subject: [PATCH 05/17] =?UTF-8?q?REFACTOR:=20RegionContainer=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 2 ++ __mocks__/react-redux.js | 2 +- src/RegionContainer.jsx | 11 ++++++----- src/RegionContainer.test.jsx | 27 ++++++++++++--------------- src/action.js | 9 ++++++++- src/reducer.js | 15 ++++++++++++--- 6 files changed, 41 insertions(+), 25 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 03110529d..665ddc46d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -52,5 +52,7 @@ module.exports = { 'react/prop-types': 'off', 'react/react-in-jsx-scope': 'off', + + 'import/no-unresolved': 'off', }, }; diff --git a/__mocks__/react-redux.js b/__mocks__/react-redux.js index 6a6ea8eba..d70cb91bf 100644 --- a/__mocks__/react-redux.js +++ b/__mocks__/react-redux.js @@ -1,3 +1,3 @@ -export const useDispatch = jest.fn(); +export const useDispatch = jest.fn((dispatch) => dispatch); export const useSelector = jest.fn((selector) => selector({})); diff --git a/src/RegionContainer.jsx b/src/RegionContainer.jsx index 6eae15132..88331ebb8 100644 --- a/src/RegionContainer.jsx +++ b/src/RegionContainer.jsx @@ -1,28 +1,29 @@ import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { fetchRegion } from './action'; +import { updateSelectedData, fetchRegion } from './action'; export default function RegionContainer() { // 데이터 페칭하기 const dispatch = useDispatch(); + const { regionData, selectedData } = useSelector((state) => state); + useEffect(() => { dispatch(fetchRegion()); }, []); - const { region } = useSelector((state) => state); function handleClickButton(item) { - console.log(item); + dispatch(updateSelectedData({ selectedRegion: item })); } return (
- {region?.regionData.map((item) => ( + {regionData?.map((item) => ( ))}
diff --git a/src/RegionContainer.test.jsx b/src/RegionContainer.test.jsx index dc3505102..f620efaff 100644 --- a/src/RegionContainer.test.jsx +++ b/src/RegionContainer.test.jsx @@ -1,27 +1,24 @@ -import { fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import { useDispatch, useSelector } from 'react-redux'; import RegionContainer from './RegionContainer'; describe('RegionContainer', () => { - // TODO - // 1. API를 받아서 데이터 받아오기 - // 2. 하위 컴포넌트에 Region data 넘겨주기 - // 3. 지역 선택시 state에 넘겨주기 - - const renderRegionContainer = () => render(); + const dispatch = jest.fn(); jest.mock('react-redux'); jest.mock('./services/fetchData'); + useDispatch.mockImplementation(() => dispatch); + useSelector.mockImplementation(() => ({ + regionData: + [ + { id: 1, name: '서울' }, + { id: 2, name: '부산' }], + })); + + const renderRegionContainer = () => render(); + it('데이터 받기', () => { const { getByText } = renderRegionContainer(); - useSelector.mockImplementation(() => [ - { id: 1, name: '서울' }, - { id: 2, name: '대전' }, - ]); - const dispatch = jest.fn(); - useDispatch.mockImplementation(() => dispatch()); - // 데이터 페칭하기 - // 페칭한 데이터 있는지 확인 expect(getByText('서울')).not.toBeNull(); fireEvent.click(getByText('서울')); expect(dispatch).toBeCalled(); diff --git a/src/action.js b/src/action.js index 1945da025..1d4092b60 100644 --- a/src/action.js +++ b/src/action.js @@ -3,7 +3,7 @@ import { regionData } from './services/fetchData'; export function setRegionData(data) { return { type: 'SET_REGION_DATA', - payload: { regionData: data }, + payload: data, }; } @@ -13,3 +13,10 @@ export function fetchRegion() { await dispatch(setRegionData(data)); }); } + +export function updateSelectedData(selectedRegion) { + return { + type: 'UPDATE_SELECTED_DATA', + payload: selectedRegion, + }; +} diff --git a/src/reducer.js b/src/reducer.js index 78fd1a9dc..1a73f8ff0 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -1,12 +1,21 @@ -const initialState = { regionData: '' }; +const initialState = { regionData: [], selectedData: {} }; const handleAction = { SET_REGION_DATA: (state, action) => { const { regionData } = action.payload; - console.log(regionData); return { ...state, - region: regionData, + regionData, + }; + }, + UPDATE_SELECTED_DATA: (state, action) => { + const { selectedRegion, selectedCategory } = action.payload; + return { + ...state, + selectedData: { + ...state.selectedData, + selectedRegion, + }, }; }, }; From 49d1a8b92d4023273fca557fb0c475bf3b2f1db4 Mon Sep 17 00:00:00 2001 From: ctaaag Date: Thu, 1 Jun 2023 17:30:09 +0900 Subject: [PATCH 06/17] =?UTF-8?q?ADD:=20Region=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Region.jsx | 15 +++++++++++++++ src/Region.test.jsx | 36 ++++++++++++++++++++++++++++++++++++ src/RegionContainer.jsx | 18 ++++++------------ 3 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 src/Region.jsx create mode 100644 src/Region.test.jsx diff --git a/src/Region.jsx b/src/Region.jsx new file mode 100644 index 000000000..de493f7fb --- /dev/null +++ b/src/Region.jsx @@ -0,0 +1,15 @@ +export default function Region({ regionData, selectedData, onClick }) { + return ( +
+ {regionData?.map((item) => ( + + ))} +
+ ); +} diff --git a/src/Region.test.jsx b/src/Region.test.jsx new file mode 100644 index 000000000..70a0401ee --- /dev/null +++ b/src/Region.test.jsx @@ -0,0 +1,36 @@ +import { render, fireEvent } from '@testing-library/react'; +import Region from './Region'; + +describe('Region', () => { + const regionData = [ + { id: 1, name: '서울' }, + { id: 2, name: '부산' }, + ]; + const selectedData = { + selectedRegion: { id: 1, name: '서울' }, + }; + + const onClick = jest.fn(); + + const renderRegion = () => render( + , + ); + + it('지역 input이 보인다.', () => { + const { getByText } = renderRegion(); + expect(getByText('부산')).not.toBeNull; + }); + it('지역 input을 클릭하면 함수를 호출한다.', () => { + const { getByText } = renderRegion(); + fireEvent.click(getByText('부산')); + expect(onClick).toBeCalledWith({ id: 2, name: '부산' }); + }); + it('선택된 input 옆에는 체크표시가 된다.', () => { + const { getByText } = renderRegion(); + expect(getByText('서울 v')).not.toBeNull; + }); +}); diff --git a/src/RegionContainer.jsx b/src/RegionContainer.jsx index 88331ebb8..80ddebe0c 100644 --- a/src/RegionContainer.jsx +++ b/src/RegionContainer.jsx @@ -1,6 +1,7 @@ import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { updateSelectedData, fetchRegion } from './action'; +import Region from './Region'; export default function RegionContainer() { // 데이터 페칭하기 @@ -16,17 +17,10 @@ export default function RegionContainer() { } return ( -
- {regionData?.map((item) => ( - - ))} -
- + ); } From 75af3f81cf7e3952c2f219298f56db0f8354dc3b Mon Sep 17 00:00:00 2001 From: ctaaag Date: Thu, 1 Jun 2023 17:52:02 +0900 Subject: [PATCH 07/17] =?UTF-8?q?ADD:=20CategoriesContainer=20test=20?= =?UTF-8?q?=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CategoriesContainer.test.jsx | 33 ++++++++++++++++++++++++++++++ src/RegionContainer.test.jsx | 2 +- src/action.js | 2 +- src/services/__mock__/api.js | 7 +++++++ src/services/__mock__/fetchData.js | 3 --- src/services/api.js | 21 +++++++++++++++++++ src/services/fetchData.js | 10 --------- 7 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 src/CategoriesContainer.test.jsx create mode 100644 src/services/__mock__/api.js delete mode 100644 src/services/__mock__/fetchData.js create mode 100644 src/services/api.js delete mode 100644 src/services/fetchData.js diff --git a/src/CategoriesContainer.test.jsx b/src/CategoriesContainer.test.jsx new file mode 100644 index 000000000..c64d6611d --- /dev/null +++ b/src/CategoriesContainer.test.jsx @@ -0,0 +1,33 @@ +import { render, fireEvent } from '@testing-library/react'; +import { useDispatch, useSelector } from 'react-redux'; +import CategoriesContainer from './CategoriesContainer'; + +describe('CategoriesContainer', () => { + // TODO: + // 1. selector에서 데이터 페칭해서 받아오기 + // 2. dispatch로 초기 데이터 렌더링하기 + // 3. input 클릭 시 함수 호출하기 + const dispatch = jest.fn(); + const renderCategoriesContainer = () => render(); + jest.mock('react-redux'); + jest.mock('./services/api'); + + useSelector.mockImplementation(() => [ + { id: 1, name: '한식' }, { id: 2, name: '양식' }, + ]); + + useDispatch.mockImplementation(() => dispatch); + + it('데이터 받기', () => { + const { getByText } = renderCategoriesContainer(); + expect(getByText('한식')).not.toBeNull(); + }); + + describe('button 클릭이 되면', () => { + it('클릭 된 버튼의 데이터를 state에 저장한다', () => { + const { getByText } = renderCategoriesContainer(); + fireEvent.click(getByText('한식')); + expect(dispatch).toBeCalled(); + }); + }); +}); diff --git a/src/RegionContainer.test.jsx b/src/RegionContainer.test.jsx index f620efaff..1532cdf80 100644 --- a/src/RegionContainer.test.jsx +++ b/src/RegionContainer.test.jsx @@ -5,7 +5,7 @@ import RegionContainer from './RegionContainer'; describe('RegionContainer', () => { const dispatch = jest.fn(); jest.mock('react-redux'); - jest.mock('./services/fetchData'); + jest.mock('./services/api'); useDispatch.mockImplementation(() => dispatch); useSelector.mockImplementation(() => ({ regionData: diff --git a/src/action.js b/src/action.js index 1d4092b60..0f190001d 100644 --- a/src/action.js +++ b/src/action.js @@ -1,4 +1,4 @@ -import { regionData } from './services/fetchData'; +import { regionData } from './services/api'; export function setRegionData(data) { return { diff --git a/src/services/__mock__/api.js b/src/services/__mock__/api.js new file mode 100644 index 000000000..9e6e5ba0d --- /dev/null +++ b/src/services/__mock__/api.js @@ -0,0 +1,7 @@ +export async function regionData() { + return []; +} + +export async function categoryData() { + return []; +} diff --git a/src/services/__mock__/fetchData.js b/src/services/__mock__/fetchData.js deleted file mode 100644 index 0425fb43e..000000000 --- a/src/services/__mock__/fetchData.js +++ /dev/null @@ -1,3 +0,0 @@ -export async function regionData() { - return []; -} diff --git a/src/services/api.js b/src/services/api.js new file mode 100644 index 000000000..125d18c33 --- /dev/null +++ b/src/services/api.js @@ -0,0 +1,21 @@ +export async function regionData() { + const url = 'https://eatgo-customer-api.ahastudio.com/regions'; + try { + const data = await fetch(url); + const result = { regionData: await data.json() }; + return result; + } catch (error) { + alert(error); + } +} + +export async function categoryData() { + const url = 'https://eatgo-customer-api.ahastudio.com/categories'; + try { + const data = await fetch(url); + const result = { regionData: await data.json() }; + return result; + } catch (error) { + alert(error); + } +} diff --git a/src/services/fetchData.js b/src/services/fetchData.js deleted file mode 100644 index 75bcb86cf..000000000 --- a/src/services/fetchData.js +++ /dev/null @@ -1,10 +0,0 @@ -export async function regionData() { - const url = 'https://eatgo-customer-api.ahastudio.com/regions'; - try { - const data = await fetch(url); - const result = { regionData: await data.json() }; - return result; - } catch (error) { - alert(error); - } -} From 7d411ecfb83abd586bc4ca79e3589f566907c036 Mon Sep 17 00:00:00 2001 From: ctaaag Date: Thu, 1 Jun 2023 18:23:47 +0900 Subject: [PATCH 08/17] =?UTF-8?q?ADD:=20CategoryConainer=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CategoriesContainer.jsx | 30 +++++++++++++++++++++++++++++- src/CategoriesContainer.test.jsx | 11 ++++------- src/RegionContainer.jsx | 2 +- src/action.js | 23 ++++++++++++++++++++--- src/reducer.js | 15 ++++++++++++++- src/services/api.js | 6 +++--- 6 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/CategoriesContainer.jsx b/src/CategoriesContainer.jsx index c9d787e77..3c0d42784 100644 --- a/src/CategoriesContainer.jsx +++ b/src/CategoriesContainer.jsx @@ -1,5 +1,33 @@ +import { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { fetchCategory, updateSelectedData } from './action'; + export default function CategoriesContainer() { + const dispatch = useDispatch(); + const { categoryData, selectedData } = useSelector((state) => state); + useEffect(() => { + dispatch(fetchCategory()); + }, []); + + function handleClickCategory(category) { + dispatch(updateSelectedData({ + selectedRegion: selectedData?.selectedRegion, + selectedCategory: category, + })); + } + return ( -
CategoriesContainer
+
+ {categoryData?.map((category) => ( + + ))} + +
); } diff --git a/src/CategoriesContainer.test.jsx b/src/CategoriesContainer.test.jsx index c64d6611d..ecf5da074 100644 --- a/src/CategoriesContainer.test.jsx +++ b/src/CategoriesContainer.test.jsx @@ -3,18 +3,15 @@ import { useDispatch, useSelector } from 'react-redux'; import CategoriesContainer from './CategoriesContainer'; describe('CategoriesContainer', () => { - // TODO: - // 1. selector에서 데이터 페칭해서 받아오기 - // 2. dispatch로 초기 데이터 렌더링하기 - // 3. input 클릭 시 함수 호출하기 const dispatch = jest.fn(); const renderCategoriesContainer = () => render(); jest.mock('react-redux'); jest.mock('./services/api'); - useSelector.mockImplementation(() => [ - { id: 1, name: '한식' }, { id: 2, name: '양식' }, - ]); + useSelector.mockImplementation(() => ({ + categoryData: + [{ id: 1, name: '한식' }, { id: 2, name: '양식' }], + })); useDispatch.mockImplementation(() => dispatch); diff --git a/src/RegionContainer.jsx b/src/RegionContainer.jsx index 80ddebe0c..0cfdaae43 100644 --- a/src/RegionContainer.jsx +++ b/src/RegionContainer.jsx @@ -13,7 +13,7 @@ export default function RegionContainer() { }, []); function handleClickButton(item) { - dispatch(updateSelectedData({ selectedRegion: item })); + dispatch(updateSelectedData({ selectedCategory: selectedData?.selectedCategory, selectedRegion: item })); } return ( diff --git a/src/action.js b/src/action.js index 0f190001d..9663ca57c 100644 --- a/src/action.js +++ b/src/action.js @@ -1,4 +1,4 @@ -import { regionData } from './services/api'; +import { regionData, categoryData } from './services/api'; export function setRegionData(data) { return { @@ -14,9 +14,26 @@ export function fetchRegion() { }); } -export function updateSelectedData(selectedRegion) { +export function setCategoryData(data) { + return { + type: 'SET_CATEGORY_DATA', + payload: data, + }; +} + +export function fetchCategory() { + return ( + async (dispatch) => { + const data = await categoryData(); + console.log('Categories', data); + await dispatch(setCategoryData(data)); + } + ); +} + +export function updateSelectedData(selectedData) { return { type: 'UPDATE_SELECTED_DATA', - payload: selectedRegion, + payload: selectedData, }; } diff --git a/src/reducer.js b/src/reducer.js index 1a73f8ff0..b42aefc2c 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -1,4 +1,4 @@ -const initialState = { regionData: [], selectedData: {} }; +const initialState = { regionData: [], categoryData: [], selectedData: {} }; const handleAction = { SET_REGION_DATA: (state, action) => { @@ -8,13 +8,26 @@ const handleAction = { regionData, }; }, + SET_CATEGORY_DATA: (state, action) => { + const { categoryData } = action.payload; + console.log('22', categoryData); + return { + ...state, + categoryData, + }; + }, UPDATE_SELECTED_DATA: (state, action) => { const { selectedRegion, selectedCategory } = action.payload; + console.log(action.payload); + console.log('selectedRegion', selectedRegion); + console.log('selectedCategory', selectedCategory); + console.log('state', state); return { ...state, selectedData: { ...state.selectedData, selectedRegion, + selectedCategory, }, }; }, diff --git a/src/services/api.js b/src/services/api.js index 125d18c33..481cd9d9b 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -5,7 +5,7 @@ export async function regionData() { const result = { regionData: await data.json() }; return result; } catch (error) { - alert(error); + return console.log(error); } } @@ -13,9 +13,9 @@ export async function categoryData() { const url = 'https://eatgo-customer-api.ahastudio.com/categories'; try { const data = await fetch(url); - const result = { regionData: await data.json() }; + const result = { categoryData: await data.json() }; return result; } catch (error) { - alert(error); + return console.log(error); } } From d32647571b1f7d42003dc45ee790bcfeaf2604d8 Mon Sep 17 00:00:00 2001 From: ctaaag Date: Thu, 1 Jun 2023 18:29:11 +0900 Subject: [PATCH 09/17] =?UTF-8?q?ADD:=20Category=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CategoriesContainer.jsx | 18 ++++++------------ src/Category.jsx | 15 +++++++++++++++ src/Category.test.jsx | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 src/Category.jsx create mode 100644 src/Category.test.jsx diff --git a/src/CategoriesContainer.jsx b/src/CategoriesContainer.jsx index 3c0d42784..9a4809c11 100644 --- a/src/CategoriesContainer.jsx +++ b/src/CategoriesContainer.jsx @@ -1,6 +1,7 @@ import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { fetchCategory, updateSelectedData } from './action'; +import Category from './Category'; export default function CategoriesContainer() { const dispatch = useDispatch(); @@ -17,17 +18,10 @@ export default function CategoriesContainer() { } return ( -
- {categoryData?.map((category) => ( - - ))} - -
+ ); } diff --git a/src/Category.jsx b/src/Category.jsx new file mode 100644 index 000000000..2f8f07ff0 --- /dev/null +++ b/src/Category.jsx @@ -0,0 +1,15 @@ +export default function Category({ categoryData, selectedData, onClick }) { + return ( + <> + {categoryData?.map((category) => ( + + ))} + + ); +} diff --git a/src/Category.test.jsx b/src/Category.test.jsx new file mode 100644 index 000000000..894fd335c --- /dev/null +++ b/src/Category.test.jsx @@ -0,0 +1,33 @@ +import { render, fireEvent } from '@testing-library/react'; +import Category from './Category'; + +describe('Region', () => { + const categoryData = [{ id: 1, name: '한식' }, { id: 2, name: '양식' }]; + const selectedData = { + selectedCategory: { id: 1, name: '한식' }, + }; + + const onClick = jest.fn(); + + const renderCategory = () => render( + , + ); + + it('지역 input이 보인다.', () => { + const { getByText } = renderCategory(); + expect(getByText('양식')).not.toBeNull; + }); + it('지역 input을 클릭하면 함수를 호출한다.', () => { + const { getByText } = renderCategory(); + fireEvent.click(getByText('양식')); + expect(onClick).toBeCalledWith({ id: 2, name: '양식' }); + }); + it('선택된 input 옆에는 체크표시가 된다.', () => { + const { getByText } = renderCategory(); + expect(getByText('한식 v')).not.toBeNull; + }); +}); From 81e463167d11a051062fa97cad325e3325ce4e79 Mon Sep 17 00:00:00 2001 From: ctaaag Date: Thu, 1 Jun 2023 19:38:10 +0900 Subject: [PATCH 10/17] =?UTF-8?q?ADD:=20RestaurantContainer=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/RestaurantsContainer.jsx | 15 ++++++++++++- src/RestaurantsContainer.test.jsx | 36 +++++++++++++++++++++++++++++++ src/action.js | 21 +++++++++++++++++- src/reducer.js | 12 ++++++++++- src/services/__mock__/api.js | 4 ++++ src/services/api.js | 13 +++++++++++ 6 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 src/RestaurantsContainer.test.jsx diff --git a/src/RestaurantsContainer.jsx b/src/RestaurantsContainer.jsx index e2b7ff640..e16e78a8a 100644 --- a/src/RestaurantsContainer.jsx +++ b/src/RestaurantsContainer.jsx @@ -1,5 +1,18 @@ +import { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { fetchRestaurant } from './action'; + export default function RestaurantsContainer() { + const { restaurantData } = useSelector((state) => state); + const { selectedRegion, selectedCategory } = useSelector((state) => state.selectedData); + const dispatch = useDispatch(); + useEffect(() => { + dispatch(fetchRestaurant({ selectedRegion, selectedCategory })); + }, [selectedRegion, selectedCategory]); + return ( -
RestaurantsContainer
+
    + {restaurantData.map((restaurant) =>
  • {restaurant.name}
  • )} +
); } diff --git a/src/RestaurantsContainer.test.jsx b/src/RestaurantsContainer.test.jsx new file mode 100644 index 000000000..b0b316bf5 --- /dev/null +++ b/src/RestaurantsContainer.test.jsx @@ -0,0 +1,36 @@ +import { render } from '@testing-library/react'; +import { useDispatch, useSelector } from 'react-redux'; +import RestaurantsContainer from './RestaurantsContainer'; + +describe('RestaurantsContainer', () => { + // TODO + // 1. api 호출하기 + // 2. 식당 리스트 보여주기 + // 3. 값이 바뀔 때마다 식당 다르게 보여주기 + const dispatch = jest.fn(); + jest.mock('react-redux'); + jest.mock('./services/api'); + + useDispatch.mockImplementation(() => dispatch); + useSelector.mockImplementation(() => ({ + restaurantData: [ + { id: 1, name: '코코식당' }, { id: 2, name: '네네식당' }, + ], + })); + + const renderRestaurantsContainer = () => render(); + + it('식당 리스트가 보인다', () => { + const { getByText } = renderRestaurantsContainer(); + expect(getByText('코코식당')).not.toBeNull(); + }); + it('선택된 식당이 바뀌면 ', () => { + useSelector.mockImplementation(() => ({ + restaurantData: [ + { id: 1, name: '바뀐식당' }, { id: 2, name: '이렇게' }, + ], + })); + const { getByText } = renderRestaurantsContainer(); + expect(getByText('바뀐식당')).not.toBeNull(); + }); +}); diff --git a/src/action.js b/src/action.js index 9663ca57c..3c24d2377 100644 --- a/src/action.js +++ b/src/action.js @@ -1,4 +1,4 @@ -import { regionData, categoryData } from './services/api'; +import { regionData, categoryData, restaurantData } from './services/api'; export function setRegionData(data) { return { @@ -31,6 +31,25 @@ export function fetchCategory() { ); } +export function setRestaurantData(data) { + return { + type: 'SET_RESTAURANT_DATA', + payload: data, + }; +} + +export function fetchRestaurant({ selectedRegion, selectedCategory }) { + return ( + async (dispatch) => { + const data = await restaurantData({ + regionName: selectedRegion.name, + categoryId: selectedCategory.id, + }); + await dispatch(setRestaurantData(data)); + } + ); +} + export function updateSelectedData(selectedData) { return { type: 'UPDATE_SELECTED_DATA', diff --git a/src/reducer.js b/src/reducer.js index b42aefc2c..58545e28f 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -1,4 +1,6 @@ -const initialState = { regionData: [], categoryData: [], selectedData: {} }; +const initialState = { + regionData: [], categoryData: [], restaurantData: [], selectedData: {}, +}; const handleAction = { SET_REGION_DATA: (state, action) => { @@ -16,6 +18,14 @@ const handleAction = { categoryData, }; }, + SET_RESTAURANT_DATA: (state, action) => { + console.log(action.payload); + const { restaurantData } = action.payload; + return { + ...state, + restaurantData, + }; + }, UPDATE_SELECTED_DATA: (state, action) => { const { selectedRegion, selectedCategory } = action.payload; console.log(action.payload); diff --git a/src/services/__mock__/api.js b/src/services/__mock__/api.js index 9e6e5ba0d..3e5752905 100644 --- a/src/services/__mock__/api.js +++ b/src/services/__mock__/api.js @@ -5,3 +5,7 @@ export async function regionData() { export async function categoryData() { return []; } + +export async function restaurantData() { + return []; +} diff --git a/src/services/api.js b/src/services/api.js index 481cd9d9b..d34c34d55 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -19,3 +19,16 @@ export async function categoryData() { return console.log(error); } } + +export async function restaurantData({ regionName, categoryId }) { + console.log('regionName:', regionName); + console.log('categoryId:', categoryId); + const url = `https://eatgo-customer-api.ahastudio.com/restaurants?region=${regionName}&category=${categoryId}`; + try { + const data = await fetch(url); + const result = { restaurantData: await data.json() }; + return result; + } catch (error) { + return console.log(error); + } +} From 0282f8854443eec0d96685afddb39920bd15fdc9 Mon Sep 17 00:00:00 2001 From: ctaaag Date: Thu, 1 Jun 2023 19:48:00 +0900 Subject: [PATCH 11/17] =?UTF-8?q?ADD:=20Restaurant=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Restaurants.jsx | 7 +++++++ src/Restaurants.test.jsx | 16 ++++++++++++++++ src/RestaurantsContainer.jsx | 3 ++- src/action.js | 1 - src/reducer.js | 9 ++------- 5 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 src/Restaurants.jsx create mode 100644 src/Restaurants.test.jsx diff --git a/src/Restaurants.jsx b/src/Restaurants.jsx new file mode 100644 index 000000000..2bb37038b --- /dev/null +++ b/src/Restaurants.jsx @@ -0,0 +1,7 @@ +export default function Restaurants({ restaurantData }) { + return ( + <> + {restaurantData?.map((restaurant) =>
  • {restaurant.name}
  • )} + + ); +} diff --git a/src/Restaurants.test.jsx b/src/Restaurants.test.jsx new file mode 100644 index 000000000..f28b3c290 --- /dev/null +++ b/src/Restaurants.test.jsx @@ -0,0 +1,16 @@ +import { render } from '@testing-library/react'; +import Restaurants from './Restaurants'; + +describe('RestaurantsContainer', () => { + const restaurantData = [ + { id: 1, name: '코코식당' }, { id: 2, name: '네네식당' }, + ]; + + const renderRestaurants = () => render(); + + it('식당 리스트가 보인다', () => { + const { getByText } = renderRestaurants(); + expect(getByText('코코식당')).not.toBeNull(); + expect(getByText('네네식당')).not.toBeNull(); + }); +}); diff --git a/src/RestaurantsContainer.jsx b/src/RestaurantsContainer.jsx index e16e78a8a..5d715f6fc 100644 --- a/src/RestaurantsContainer.jsx +++ b/src/RestaurantsContainer.jsx @@ -1,6 +1,7 @@ import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { fetchRestaurant } from './action'; +import Restaurants from './Restaurants'; export default function RestaurantsContainer() { const { restaurantData } = useSelector((state) => state); @@ -12,7 +13,7 @@ export default function RestaurantsContainer() { return (
      - {restaurantData.map((restaurant) =>
    • {restaurant.name}
    • )} +
    ); } diff --git a/src/action.js b/src/action.js index 3c24d2377..385184d5b 100644 --- a/src/action.js +++ b/src/action.js @@ -25,7 +25,6 @@ export function fetchCategory() { return ( async (dispatch) => { const data = await categoryData(); - console.log('Categories', data); await dispatch(setCategoryData(data)); } ); diff --git a/src/reducer.js b/src/reducer.js index 58545e28f..6f7b7a3c7 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -12,14 +12,13 @@ const handleAction = { }, SET_CATEGORY_DATA: (state, action) => { const { categoryData } = action.payload; - console.log('22', categoryData); + return { ...state, categoryData, }; }, SET_RESTAURANT_DATA: (state, action) => { - console.log(action.payload); const { restaurantData } = action.payload; return { ...state, @@ -28,10 +27,7 @@ const handleAction = { }, UPDATE_SELECTED_DATA: (state, action) => { const { selectedRegion, selectedCategory } = action.payload; - console.log(action.payload); - console.log('selectedRegion', selectedRegion); - console.log('selectedCategory', selectedCategory); - console.log('state', state); + return { ...state, selectedData: { @@ -45,7 +41,6 @@ const handleAction = { export default function store(state = initialState, action) { if (handleAction[action.type]) { - console.log(action.type); return handleAction[action.type](state, action); } return state; From 3f8015d062089e8ec0eb72289c77a8ae29634319 Mon Sep 17 00:00:00 2001 From: ctaaag Date: Fri, 2 Jun 2023 17:03:12 +0900 Subject: [PATCH 12/17] =?UTF-8?q?ADD:=20App=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.test.jsx | 76 +++++++++++++++++++++++++++++++ src/RestaurantsContainer.test.jsx | 6 +-- src/reducer.test.js | 0 src/services/api.js | 2 - 4 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 src/App.test.jsx create mode 100644 src/reducer.test.js diff --git a/src/App.test.jsx b/src/App.test.jsx new file mode 100644 index 000000000..bf6e2b4d2 --- /dev/null +++ b/src/App.test.jsx @@ -0,0 +1,76 @@ +import { fireEvent, render } from '@testing-library/react'; +import { useDispatch, useSelector } from 'react-redux'; +import App from './App'; + +describe('App', () => { + jest.mock('react-redux'); + jest.mock('./services/api'); + + const dispatch = jest.fn(); + + useDispatch.mockImplementation(() => dispatch); + useSelector.mockImplementation(() => ({ + regionData: [{ id: 1, name: '서울' }, { id: 2, name: '부산' }], + categoryData: [{ id: 1, name: '한식' }, { id: 2, name: '양식' }], + restaurantData: [{ id: 1, name: '코코식당' }, { id: 2, name: '네네식당' }], + selectedData: { selectedRegion: { id: 1, name: '서울' }, selectedCategory: { id: 1, name: '한식' } }, + })); + + const renderApp = () => render(); + + describe('RegionContainer가 렌더링 된다.', () => { + it('지역정보들이 input으로 보인다.', () => { + const { getByText } = renderApp(); + expect(getByText(/부산/)).not.toBeNull(); + expect(getByText(/서울/)).not.toBeNull(); + }); + + it('input 클릭시 상태가 업데이트된다.', () => { + const { getByText } = renderApp(); + fireEvent.click(getByText(/서울/)); + expect(dispatch).toBeCalled(); + }); + + it('선택된 input은 v가 표시된다.', () => { + const { getByText } = renderApp(); + expect(getByText('서울 v')).not.toBeNull(); + }); + }); + + describe('CategoryContainer가 렌더링 된다.', () => { + it('카테고리 목록이 input으로 보인다.', () => { + const { getByText } = renderApp(); + expect(getByText(/한식/)).not.toBeNull(); + expect(getByText(/양식/)).not.toBeNull(); + }); + + it('카테고리 input 클릭시 상태가 업데이트된다.', () => { + const { getByText } = renderApp(); + fireEvent.click(getByText(/한식/)); + expect(dispatch).toBeCalled(); + }); + + it('선택된 input은 v가 표시된다.', () => { + const { getByText } = renderApp(); + expect(getByText('한식 v')).not.toBeNull(); + }); + }); + + describe('RestaurantContainer가 렌더링 된다.', () => { + it('불러온 레스토랑 데이터들이 보인다.', () => { + const { getByText } = renderApp(); + expect(getByText(/코코식당/)).not.toBeNull(); + expect(getByText(/네네식당/)).not.toBeNull(); + }); + it('선택된 식당이 바뀌면 ', () => { + useSelector.mockImplementation(() => ({ + restaurantData: [ + { id: 1, name: '바뀐식당' }, { id: 2, name: '무슨식당' }, + ], + })); + const { getByText } = renderApp(); + expect(getByText('바뀐식당')).not.toBeNull(); + expect(getByText('무슨식당')).not.toBeNull(); + }); + }); +}); diff --git a/src/RestaurantsContainer.test.jsx b/src/RestaurantsContainer.test.jsx index b0b316bf5..b3dd12c95 100644 --- a/src/RestaurantsContainer.test.jsx +++ b/src/RestaurantsContainer.test.jsx @@ -3,10 +3,6 @@ import { useDispatch, useSelector } from 'react-redux'; import RestaurantsContainer from './RestaurantsContainer'; describe('RestaurantsContainer', () => { - // TODO - // 1. api 호출하기 - // 2. 식당 리스트 보여주기 - // 3. 값이 바뀔 때마다 식당 다르게 보여주기 const dispatch = jest.fn(); jest.mock('react-redux'); jest.mock('./services/api'); @@ -24,7 +20,7 @@ describe('RestaurantsContainer', () => { const { getByText } = renderRestaurantsContainer(); expect(getByText('코코식당')).not.toBeNull(); }); - it('선택된 식당이 바뀌면 ', () => { + it('선택된 식당이 변경되면 해당 식당이 보인다. ', () => { useSelector.mockImplementation(() => ({ restaurantData: [ { id: 1, name: '바뀐식당' }, { id: 2, name: '이렇게' }, diff --git a/src/reducer.test.js b/src/reducer.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/src/services/api.js b/src/services/api.js index d34c34d55..84b184495 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -21,8 +21,6 @@ export async function categoryData() { } export async function restaurantData({ regionName, categoryId }) { - console.log('regionName:', regionName); - console.log('categoryId:', categoryId); const url = `https://eatgo-customer-api.ahastudio.com/restaurants?region=${regionName}&category=${categoryId}`; try { const data = await fetch(url); From 46edcc7c75a39f05fdaf01910ef11dccd118235c Mon Sep 17 00:00:00 2001 From: ctaaag Date: Fri, 2 Jun 2023 17:30:41 +0900 Subject: [PATCH 13/17] =?UTF-8?q?ADD:=20reducer=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/action.js | 2 +- src/reducer.js | 3 +-- src/reducer.test.js | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/action.js b/src/action.js index 385184d5b..0ac3cf73b 100644 --- a/src/action.js +++ b/src/action.js @@ -49,7 +49,7 @@ export function fetchRestaurant({ selectedRegion, selectedCategory }) { ); } -export function updateSelectedData(selectedData) { +export function updateSelectedData({ selectedData }) { return { type: 'UPDATE_SELECTED_DATA', payload: selectedData, diff --git a/src/reducer.js b/src/reducer.js index 6f7b7a3c7..d42890a25 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -27,7 +27,6 @@ const handleAction = { }, UPDATE_SELECTED_DATA: (state, action) => { const { selectedRegion, selectedCategory } = action.payload; - return { ...state, selectedData: { @@ -39,7 +38,7 @@ const handleAction = { }, }; -export default function store(state = initialState, action) { +export default function reducer(state = initialState, action) { if (handleAction[action.type]) { return handleAction[action.type](state, action); } diff --git a/src/reducer.test.js b/src/reducer.test.js index e69de29bb..0615f956e 100644 --- a/src/reducer.test.js +++ b/src/reducer.test.js @@ -0,0 +1,41 @@ +import reducer from './reducer'; +import { + setRegionData, setCategoryData, setRestaurantData, updateSelectedData, +} from './action'; + +describe('reducer', () => { + const prevState = { + regionData: [], categoryData: [], restaurantData: [], selectedData: {}, + }; + describe('fetch된 Region 데이터가 변경되었을 때', () => { + it('변경된 region state가 반환된다.', () => { + const state = reducer(prevState, setRegionData({ regionData: [{ id: 1, name: '서울' }] })); + expect(state.regionData[0].name).toBe('서울'); + }); + }); + describe('fetch된 Category 데이터가 변경되었을 때', () => { + it('변경된 region state가 반환된다.', () => { + const state = reducer(prevState, setCategoryData({ categoryData: [{ id: 1, name: '한식' }] })); + expect(state.categoryData[0].name).toBe('한식'); + }); + }); + describe('fetch된 Restaurant 데이터가 변경되었을 때', () => { + it('변경된 region state가 반환된다.', () => { + const state = reducer(prevState, setRestaurantData({ restaurantData: [{ id: 1, name: '함지박식당' }] })); + expect(state.restaurantData[0].name).toBe('함지박식당'); + }); + }); + describe('클릭한 region과 category가 변경되었을 때 ', () => { + it('변경된 region state가 반환된다.', () => { + const state = reducer(prevState, updateSelectedData({ + selectedData: { + selectedRegion: { id: 1, name: '서울' }, + selectedCategory: { id: 1, name: '한식' }, + }, + })); + + expect(state.selectedData.selectedRegion.name).toBe('서울'); + expect(state.selectedData.selectedCategory.name).toBe('한식'); + }); + }); +}); From 4f8ccdb99df807c33c97a4b0a63e501eef2a187e Mon Sep 17 00:00:00 2001 From: ctaaag Date: Wed, 7 Jun 2023 21:26:18 +0900 Subject: [PATCH 14/17] =?UTF-8?q?Refactor=20:=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EB=B0=8F=20try-catch=EB=AC=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.test.jsx | 16 +++++---- src/CategoriesContainer.jsx | 1 + src/CategoriesContainer.test.jsx | 1 + src/RegionContainer.jsx | 5 ++- src/action.js | 16 ++++++--- src/reducer.js | 58 +++++++++++++------------------- src/reducer.test.js | 8 ++--- src/services/__mock__/api.js | 6 ++-- src/services/api.js | 36 +++++++------------- 9 files changed, 69 insertions(+), 78 deletions(-) diff --git a/src/App.test.jsx b/src/App.test.jsx index bf6e2b4d2..b253c6ddf 100644 --- a/src/App.test.jsx +++ b/src/App.test.jsx @@ -8,12 +8,17 @@ describe('App', () => { const dispatch = jest.fn(); + const regionData = [{ id: 1, name: '서울' }, { id: 2, name: '부산' }]; + const categoryData = [{ id: 1, name: '한식' }, { id: 2, name: '양식' }]; + const restaurantData = [{ id: 1, name: '코코식당' }, { id: 2, name: '네네식당' }]; + const selectedData = { selectedRegion: { id: 1, name: '서울' }, selectedCategory: { id: 1, name: '한식' } }; + useDispatch.mockImplementation(() => dispatch); useSelector.mockImplementation(() => ({ - regionData: [{ id: 1, name: '서울' }, { id: 2, name: '부산' }], - categoryData: [{ id: 1, name: '한식' }, { id: 2, name: '양식' }], - restaurantData: [{ id: 1, name: '코코식당' }, { id: 2, name: '네네식당' }], - selectedData: { selectedRegion: { id: 1, name: '서울' }, selectedCategory: { id: 1, name: '한식' } }, + regionData, + categoryData, + restaurantData, + selectedData, })); const renderApp = () => render(); @@ -21,8 +26,7 @@ describe('App', () => { describe('RegionContainer가 렌더링 된다.', () => { it('지역정보들이 input으로 보인다.', () => { const { getByText } = renderApp(); - expect(getByText(/부산/)).not.toBeNull(); - expect(getByText(/서울/)).not.toBeNull(); + regionData.forEach((region) => expect(getByText(new RegExp(`[${region.name}]`))).not.toBeNull()); }); it('input 클릭시 상태가 업데이트된다.', () => { diff --git a/src/CategoriesContainer.jsx b/src/CategoriesContainer.jsx index 9a4809c11..2ae377f5a 100644 --- a/src/CategoriesContainer.jsx +++ b/src/CategoriesContainer.jsx @@ -6,6 +6,7 @@ import Category from './Category'; export default function CategoriesContainer() { const dispatch = useDispatch(); const { categoryData, selectedData } = useSelector((state) => state); + useEffect(() => { dispatch(fetchCategory()); }, []); diff --git a/src/CategoriesContainer.test.jsx b/src/CategoriesContainer.test.jsx index ecf5da074..981f8b269 100644 --- a/src/CategoriesContainer.test.jsx +++ b/src/CategoriesContainer.test.jsx @@ -5,6 +5,7 @@ import CategoriesContainer from './CategoriesContainer'; describe('CategoriesContainer', () => { const dispatch = jest.fn(); const renderCategoriesContainer = () => render(); + jest.mock('react-redux'); jest.mock('./services/api'); diff --git a/src/RegionContainer.jsx b/src/RegionContainer.jsx index 0cfdaae43..e15c5a3ef 100644 --- a/src/RegionContainer.jsx +++ b/src/RegionContainer.jsx @@ -13,7 +13,10 @@ export default function RegionContainer() { }, []); function handleClickButton(item) { - dispatch(updateSelectedData({ selectedCategory: selectedData?.selectedCategory, selectedRegion: item })); + dispatch(updateSelectedData({ + selectedCategory: selectedData?.selectedCategory, + selectedRegion: item, + })); } return ( diff --git a/src/action.js b/src/action.js index 0ac3cf73b..582465bfd 100644 --- a/src/action.js +++ b/src/action.js @@ -1,4 +1,4 @@ -import { regionData, categoryData, restaurantData } from './services/api'; +import { fetchRegions, fetchCategories, fetchRestaurants } from './services/api'; export function setRegionData(data) { return { @@ -9,7 +9,7 @@ export function setRegionData(data) { export function fetchRegion() { return (async (dispatch) => { - const data = await regionData(); + const data = await fetchRegions(); await dispatch(setRegionData(data)); }); } @@ -24,7 +24,7 @@ export function setCategoryData(data) { export function fetchCategory() { return ( async (dispatch) => { - const data = await categoryData(); + const data = await fetchCategories(); await dispatch(setCategoryData(data)); } ); @@ -38,9 +38,15 @@ export function setRestaurantData(data) { } export function fetchRestaurant({ selectedRegion, selectedCategory }) { + const isEmptyObject = (object) => Object.keys(object || {}).length === 0; + + if (isEmptyObject(selectedRegion) || isEmptyObject(selectedCategory)) { + return setRestaurantData({ restaurantData: [{ id: 0, name: '불러온 레스토랑 목록이 없습니다.' }] }); + } + return ( async (dispatch) => { - const data = await restaurantData({ + const data = await fetchRestaurants({ regionName: selectedRegion.name, categoryId: selectedCategory.id, }); @@ -49,7 +55,7 @@ export function fetchRestaurant({ selectedRegion, selectedCategory }) { ); } -export function updateSelectedData({ selectedData }) { +export function updateSelectedData(selectedData) { return { type: 'UPDATE_SELECTED_DATA', payload: selectedData, diff --git a/src/reducer.js b/src/reducer.js index d42890a25..403c97974 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -1,41 +1,31 @@ const initialState = { - regionData: [], categoryData: [], restaurantData: [], selectedData: {}, + regionData: [], + categoryData: [], + restaurantData: [], + selectedData: { selectedRegion: {}, selectedCategory: {} }, }; const handleAction = { - SET_REGION_DATA: (state, action) => { - const { regionData } = action.payload; - return { - ...state, - regionData, - }; - }, - SET_CATEGORY_DATA: (state, action) => { - const { categoryData } = action.payload; - - return { - ...state, - categoryData, - }; - }, - SET_RESTAURANT_DATA: (state, action) => { - const { restaurantData } = action.payload; - return { - ...state, - restaurantData, - }; - }, - UPDATE_SELECTED_DATA: (state, action) => { - const { selectedRegion, selectedCategory } = action.payload; - return { - ...state, - selectedData: { - ...state.selectedData, - selectedRegion, - selectedCategory, - }, - }; - }, + SET_REGION_DATA: (state, { payload: { regionData } }) => ({ + ...state, + regionData, + }), + SET_CATEGORY_DATA: (state, { payload: { categoryData } }) => ({ + ...state, + categoryData, + }), + SET_RESTAURANT_DATA: (state, { payload: { restaurantData } }) => ({ + ...state, + restaurantData, + }), + UPDATE_SELECTED_DATA: (state, { payload: { selectedRegion, selectedCategory } }) => ({ + ...state, + selectedData: { + ...state.selectedData, + selectedRegion, + selectedCategory, + }, + }), }; export default function reducer(state = initialState, action) { diff --git a/src/reducer.test.js b/src/reducer.test.js index 0615f956e..84d563525 100644 --- a/src/reducer.test.js +++ b/src/reducer.test.js @@ -5,7 +5,7 @@ import { describe('reducer', () => { const prevState = { - regionData: [], categoryData: [], restaurantData: [], selectedData: {}, + regionData: [], categoryData: [], restaurantData: [], selectedData: { selectedRegion: {}, selectedCategory: {} }, }; describe('fetch된 Region 데이터가 변경되었을 때', () => { it('변경된 region state가 반환된다.', () => { @@ -28,10 +28,8 @@ describe('reducer', () => { describe('클릭한 region과 category가 변경되었을 때 ', () => { it('변경된 region state가 반환된다.', () => { const state = reducer(prevState, updateSelectedData({ - selectedData: { - selectedRegion: { id: 1, name: '서울' }, - selectedCategory: { id: 1, name: '한식' }, - }, + selectedRegion: { id: 1, name: '서울' }, + selectedCategory: { id: 1, name: '한식' }, })); expect(state.selectedData.selectedRegion.name).toBe('서울'); diff --git a/src/services/__mock__/api.js b/src/services/__mock__/api.js index 3e5752905..9ad47a808 100644 --- a/src/services/__mock__/api.js +++ b/src/services/__mock__/api.js @@ -1,11 +1,11 @@ -export async function regionData() { +export async function fetchRegions() { return []; } -export async function categoryData() { +export async function fetchCategories() { return []; } -export async function restaurantData() { +export async function fetchRestaurants() { return []; } diff --git a/src/services/api.js b/src/services/api.js index 84b184495..0912269a6 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -1,32 +1,20 @@ -export async function regionData() { +export async function fetchRegions() { const url = 'https://eatgo-customer-api.ahastudio.com/regions'; - try { - const data = await fetch(url); - const result = { regionData: await data.json() }; - return result; - } catch (error) { - return console.log(error); - } + const data = await fetch(url); + const result = { regionData: await data.json() }; + return result; } -export async function categoryData() { +export async function fetchCategories() { const url = 'https://eatgo-customer-api.ahastudio.com/categories'; - try { - const data = await fetch(url); - const result = { categoryData: await data.json() }; - return result; - } catch (error) { - return console.log(error); - } + const data = await fetch(url); + const result = { categoryData: await data.json() }; + return result; } -export async function restaurantData({ regionName, categoryId }) { +export async function fetchRestaurants({ regionName, categoryId }) { const url = `https://eatgo-customer-api.ahastudio.com/restaurants?region=${regionName}&category=${categoryId}`; - try { - const data = await fetch(url); - const result = { restaurantData: await data.json() }; - return result; - } catch (error) { - return console.log(error); - } + const data = await fetch(url); + const result = { restaurantData: await data.json() }; + return result; } From 9a44c87488a4b2381825e7878e378376f6334497 Mon Sep 17 00:00:00 2001 From: ctaaag Date: Wed, 7 Jun 2023 21:51:45 +0900 Subject: [PATCH 15/17] =?UTF-8?q?Refactor=20:=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EB=B0=8F=20=ED=95=A8=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.test.jsx | 20 ++++++++++---------- src/CategoriesContainer.jsx | 14 +++++++------- src/CategoriesContainer.test.jsx | 2 +- src/Category.jsx | 6 +++--- src/Category.test.jsx | 12 ++++++------ src/Region.jsx | 6 +++--- src/Region.test.jsx | 12 ++++++------ src/RegionContainer.jsx | 14 +++++++------- src/RegionContainer.test.jsx | 2 +- src/Restaurants.jsx | 4 ++-- src/Restaurants.test.jsx | 4 ++-- src/RestaurantsContainer.jsx | 8 ++++---- src/RestaurantsContainer.test.jsx | 4 ++-- src/action.js | 30 +++++++++++++++--------------- src/reducer.js | 26 +++++++++++++------------- src/reducer.test.js | 26 +++++++++++++++----------- 16 files changed, 97 insertions(+), 93 deletions(-) diff --git a/src/App.test.jsx b/src/App.test.jsx index b253c6ddf..03b36d150 100644 --- a/src/App.test.jsx +++ b/src/App.test.jsx @@ -8,17 +8,17 @@ describe('App', () => { const dispatch = jest.fn(); - const regionData = [{ id: 1, name: '서울' }, { id: 2, name: '부산' }]; - const categoryData = [{ id: 1, name: '한식' }, { id: 2, name: '양식' }]; - const restaurantData = [{ id: 1, name: '코코식당' }, { id: 2, name: '네네식당' }]; - const selectedData = { selectedRegion: { id: 1, name: '서울' }, selectedCategory: { id: 1, name: '한식' } }; + const regions = [{ id: 1, name: '서울' }, { id: 2, name: '부산' }]; + const categories = [{ id: 1, name: '한식' }, { id: 2, name: '양식' }]; + const restaurants = [{ id: 1, name: '코코식당' }, { id: 2, name: '네네식당' }]; + const selectedRegionAndCategory = { selectedRegion: { id: 1, name: '서울' }, selectedCategory: { id: 1, name: '한식' } }; useDispatch.mockImplementation(() => dispatch); useSelector.mockImplementation(() => ({ - regionData, - categoryData, - restaurantData, - selectedData, + regions, + categories, + restaurants, + selectedRegionAndCategory, })); const renderApp = () => render(); @@ -26,7 +26,7 @@ describe('App', () => { describe('RegionContainer가 렌더링 된다.', () => { it('지역정보들이 input으로 보인다.', () => { const { getByText } = renderApp(); - regionData.forEach((region) => expect(getByText(new RegExp(`[${region.name}]`))).not.toBeNull()); + regions.forEach((region) => expect(getByText(new RegExp(`[${region.name}]`))).not.toBeNull()); }); it('input 클릭시 상태가 업데이트된다.', () => { @@ -68,7 +68,7 @@ describe('App', () => { }); it('선택된 식당이 바뀌면 ', () => { useSelector.mockImplementation(() => ({ - restaurantData: [ + restaurants: [ { id: 1, name: '바뀐식당' }, { id: 2, name: '무슨식당' }, ], })); diff --git a/src/CategoriesContainer.jsx b/src/CategoriesContainer.jsx index 2ae377f5a..81b2e7a40 100644 --- a/src/CategoriesContainer.jsx +++ b/src/CategoriesContainer.jsx @@ -1,27 +1,27 @@ import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { fetchCategory, updateSelectedData } from './action'; +import { loadCategories, setSelectedRegionAndCategory } from './action'; import Category from './Category'; export default function CategoriesContainer() { const dispatch = useDispatch(); - const { categoryData, selectedData } = useSelector((state) => state); + const { categories, selectedRegionAndCategory } = useSelector((state) => state); useEffect(() => { - dispatch(fetchCategory()); + dispatch(loadCategories()); }, []); function handleClickCategory(category) { - dispatch(updateSelectedData({ - selectedRegion: selectedData?.selectedRegion, + dispatch(setSelectedRegionAndCategory({ + selectedRegion: selectedRegionAndCategory?.selectedRegion, selectedCategory: category, })); } return ( ); diff --git a/src/CategoriesContainer.test.jsx b/src/CategoriesContainer.test.jsx index 981f8b269..c7a3cc5e0 100644 --- a/src/CategoriesContainer.test.jsx +++ b/src/CategoriesContainer.test.jsx @@ -10,7 +10,7 @@ describe('CategoriesContainer', () => { jest.mock('./services/api'); useSelector.mockImplementation(() => ({ - categoryData: + categories: [{ id: 1, name: '한식' }, { id: 2, name: '양식' }], })); diff --git a/src/Category.jsx b/src/Category.jsx index 2f8f07ff0..c0aee57fa 100644 --- a/src/Category.jsx +++ b/src/Category.jsx @@ -1,13 +1,13 @@ -export default function Category({ categoryData, selectedData, onClick }) { +export default function Category({ categories, selectedRegionAndCategory, onClick }) { return ( <> - {categoryData?.map((category) => ( + {categories?.map((category) => ( ))} diff --git a/src/Category.test.jsx b/src/Category.test.jsx index 894fd335c..ae9c190cb 100644 --- a/src/Category.test.jsx +++ b/src/Category.test.jsx @@ -2,8 +2,8 @@ import { render, fireEvent } from '@testing-library/react'; import Category from './Category'; describe('Region', () => { - const categoryData = [{ id: 1, name: '한식' }, { id: 2, name: '양식' }]; - const selectedData = { + const categories = [{ id: 1, name: '한식' }, { id: 2, name: '양식' }]; + const selectedRegionAndCategory = { selectedCategory: { id: 1, name: '한식' }, }; @@ -11,15 +11,15 @@ describe('Region', () => { const renderCategory = () => render( , ); it('지역 input이 보인다.', () => { const { getByText } = renderCategory(); - expect(getByText('양식')).not.toBeNull; + expect(getByText('양식')).not.toBeNull(); }); it('지역 input을 클릭하면 함수를 호출한다.', () => { const { getByText } = renderCategory(); @@ -28,6 +28,6 @@ describe('Region', () => { }); it('선택된 input 옆에는 체크표시가 된다.', () => { const { getByText } = renderCategory(); - expect(getByText('한식 v')).not.toBeNull; + expect(getByText('한식 v')).not.toBeNull(); }); }); diff --git a/src/Region.jsx b/src/Region.jsx index de493f7fb..2ed4e44b2 100644 --- a/src/Region.jsx +++ b/src/Region.jsx @@ -1,13 +1,13 @@ -export default function Region({ regionData, selectedData, onClick }) { +export default function Region({ regions, selectedRegionAndCategory, onClick }) { return (
    - {regionData?.map((item) => ( + {regions?.map((item) => ( ))}
    diff --git a/src/Region.test.jsx b/src/Region.test.jsx index 70a0401ee..813a94de3 100644 --- a/src/Region.test.jsx +++ b/src/Region.test.jsx @@ -2,11 +2,11 @@ import { render, fireEvent } from '@testing-library/react'; import Region from './Region'; describe('Region', () => { - const regionData = [ + const regions = [ { id: 1, name: '서울' }, { id: 2, name: '부산' }, ]; - const selectedData = { + const selectedRegionAndCategory = { selectedRegion: { id: 1, name: '서울' }, }; @@ -14,15 +14,15 @@ describe('Region', () => { const renderRegion = () => render( , ); it('지역 input이 보인다.', () => { const { getByText } = renderRegion(); - expect(getByText('부산')).not.toBeNull; + expect(getByText('부산')).not.toBeNull(); }); it('지역 input을 클릭하면 함수를 호출한다.', () => { const { getByText } = renderRegion(); @@ -31,6 +31,6 @@ describe('Region', () => { }); it('선택된 input 옆에는 체크표시가 된다.', () => { const { getByText } = renderRegion(); - expect(getByText('서울 v')).not.toBeNull; + expect(getByText('서울 v')).not.toBeNull(); }); }); diff --git a/src/RegionContainer.jsx b/src/RegionContainer.jsx index e15c5a3ef..1eeb77ff8 100644 --- a/src/RegionContainer.jsx +++ b/src/RegionContainer.jsx @@ -1,28 +1,28 @@ import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { updateSelectedData, fetchRegion } from './action'; +import { setSelectedRegionAndCategory, loadRegions } from './action'; import Region from './Region'; export default function RegionContainer() { // 데이터 페칭하기 const dispatch = useDispatch(); - const { regionData, selectedData } = useSelector((state) => state); + const { regions, selectedRegionAndCategory } = useSelector((state) => state); useEffect(() => { - dispatch(fetchRegion()); + dispatch(loadRegions()); }, []); function handleClickButton(item) { - dispatch(updateSelectedData({ - selectedCategory: selectedData?.selectedCategory, + dispatch(setSelectedRegionAndCategory({ + selectedCategory: selectedRegionAndCategory?.selectedCategory, selectedRegion: item, })); } return ( ); diff --git a/src/RegionContainer.test.jsx b/src/RegionContainer.test.jsx index 1532cdf80..c0ae48e20 100644 --- a/src/RegionContainer.test.jsx +++ b/src/RegionContainer.test.jsx @@ -8,7 +8,7 @@ describe('RegionContainer', () => { jest.mock('./services/api'); useDispatch.mockImplementation(() => dispatch); useSelector.mockImplementation(() => ({ - regionData: + regions: [ { id: 1, name: '서울' }, { id: 2, name: '부산' }], diff --git a/src/Restaurants.jsx b/src/Restaurants.jsx index 2bb37038b..2a6413713 100644 --- a/src/Restaurants.jsx +++ b/src/Restaurants.jsx @@ -1,7 +1,7 @@ -export default function Restaurants({ restaurantData }) { +export default function Restaurants({ restaurants }) { return ( <> - {restaurantData?.map((restaurant) =>
  • {restaurant.name}
  • )} + {restaurants?.map((restaurant) =>
  • {restaurant.name}
  • )} ); } diff --git a/src/Restaurants.test.jsx b/src/Restaurants.test.jsx index f28b3c290..130081856 100644 --- a/src/Restaurants.test.jsx +++ b/src/Restaurants.test.jsx @@ -2,11 +2,11 @@ import { render } from '@testing-library/react'; import Restaurants from './Restaurants'; describe('RestaurantsContainer', () => { - const restaurantData = [ + const restaurants = [ { id: 1, name: '코코식당' }, { id: 2, name: '네네식당' }, ]; - const renderRestaurants = () => render(); + const renderRestaurants = () => render(); it('식당 리스트가 보인다', () => { const { getByText } = renderRestaurants(); diff --git a/src/RestaurantsContainer.jsx b/src/RestaurantsContainer.jsx index 5d715f6fc..690031f4b 100644 --- a/src/RestaurantsContainer.jsx +++ b/src/RestaurantsContainer.jsx @@ -1,19 +1,19 @@ import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { fetchRestaurant } from './action'; +import { loadRestaurants } from './action'; import Restaurants from './Restaurants'; export default function RestaurantsContainer() { - const { restaurantData } = useSelector((state) => state); + const { restaurants } = useSelector((state) => state); const { selectedRegion, selectedCategory } = useSelector((state) => state.selectedData); const dispatch = useDispatch(); useEffect(() => { - dispatch(fetchRestaurant({ selectedRegion, selectedCategory })); + dispatch(loadRestaurants({ selectedRegion, selectedCategory })); }, [selectedRegion, selectedCategory]); return (
      - +
    ); } diff --git a/src/RestaurantsContainer.test.jsx b/src/RestaurantsContainer.test.jsx index b3dd12c95..97ae79446 100644 --- a/src/RestaurantsContainer.test.jsx +++ b/src/RestaurantsContainer.test.jsx @@ -9,7 +9,7 @@ describe('RestaurantsContainer', () => { useDispatch.mockImplementation(() => dispatch); useSelector.mockImplementation(() => ({ - restaurantData: [ + restaurants: [ { id: 1, name: '코코식당' }, { id: 2, name: '네네식당' }, ], })); @@ -22,7 +22,7 @@ describe('RestaurantsContainer', () => { }); it('선택된 식당이 변경되면 해당 식당이 보인다. ', () => { useSelector.mockImplementation(() => ({ - restaurantData: [ + restaurants: [ { id: 1, name: '바뀐식당' }, { id: 2, name: '이렇게' }, ], })); diff --git a/src/action.js b/src/action.js index 582465bfd..ccfffcb7a 100644 --- a/src/action.js +++ b/src/action.js @@ -1,47 +1,47 @@ import { fetchRegions, fetchCategories, fetchRestaurants } from './services/api'; -export function setRegionData(data) { +export function setRegions(data) { return { - type: 'SET_REGION_DATA', + type: 'SET_REGIONS', payload: data, }; } -export function fetchRegion() { +export function loadRegions() { return (async (dispatch) => { const data = await fetchRegions(); - await dispatch(setRegionData(data)); + await dispatch(setRegions(data)); }); } -export function setCategoryData(data) { +export function setCategories(data) { return { - type: 'SET_CATEGORY_DATA', + type: 'SET_CATEGORIES', payload: data, }; } -export function fetchCategory() { +export function loadCategories() { return ( async (dispatch) => { const data = await fetchCategories(); - await dispatch(setCategoryData(data)); + await dispatch(setCategories(data)); } ); } -export function setRestaurantData(data) { +export function setRestaurants(data) { return { - type: 'SET_RESTAURANT_DATA', + type: 'SET_RESTAURANTS', payload: data, }; } -export function fetchRestaurant({ selectedRegion, selectedCategory }) { +export function loadRestaurants({ selectedRegion, selectedCategory }) { const isEmptyObject = (object) => Object.keys(object || {}).length === 0; if (isEmptyObject(selectedRegion) || isEmptyObject(selectedCategory)) { - return setRestaurantData({ restaurantData: [{ id: 0, name: '불러온 레스토랑 목록이 없습니다.' }] }); + return setRestaurants({ restaurantData: [{ id: 0, name: '불러온 레스토랑 목록이 없습니다.' }] }); } return ( @@ -50,14 +50,14 @@ export function fetchRestaurant({ selectedRegion, selectedCategory }) { regionName: selectedRegion.name, categoryId: selectedCategory.id, }); - await dispatch(setRestaurantData(data)); + await dispatch(setRestaurants(data)); } ); } -export function updateSelectedData(selectedData) { +export function setSelectedRegionAndCategory(selectedData) { return { - type: 'UPDATE_SELECTED_DATA', + type: 'SET_SELECTED_REGION_AND_CATEGORY', payload: selectedData, }; } diff --git a/src/reducer.js b/src/reducer.js index 403c97974..7dbbae987 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -1,27 +1,27 @@ const initialState = { - regionData: [], - categoryData: [], - restaurantData: [], - selectedData: { selectedRegion: {}, selectedCategory: {} }, + regions: [], + categories: [], + restaurants: [], + selectedRegionAndCategory: { selectedRegion: {}, selectedCategory: {} }, }; const handleAction = { - SET_REGION_DATA: (state, { payload: { regionData } }) => ({ + SET_REGIONS: (state, { payload: { regions } }) => ({ ...state, - regionData, + regions, }), - SET_CATEGORY_DATA: (state, { payload: { categoryData } }) => ({ + SET_CATEGORIES: (state, { payload: { categories } }) => ({ ...state, - categoryData, + categories, }), - SET_RESTAURANT_DATA: (state, { payload: { restaurantData } }) => ({ + SET_RESTAURANTS: (state, { payload: { restaurants } }) => ({ ...state, - restaurantData, + restaurants, }), - UPDATE_SELECTED_DATA: (state, { payload: { selectedRegion, selectedCategory } }) => ({ + SET_SELECTED_REGION_AND_CATEGORY: (state, { payload: { selectedRegion, selectedCategory } }) => ({ ...state, - selectedData: { - ...state.selectedData, + selectedRegionAndCategory: { + ...state.selectedRegionAndCategory, selectedRegion, selectedCategory, }, diff --git a/src/reducer.test.js b/src/reducer.test.js index 84d563525..7981825c0 100644 --- a/src/reducer.test.js +++ b/src/reducer.test.js @@ -1,39 +1,43 @@ import reducer from './reducer'; import { - setRegionData, setCategoryData, setRestaurantData, updateSelectedData, + setRegions, setCategories, setRestaurants, setSelectedRegionAndCategory, } from './action'; describe('reducer', () => { const prevState = { - regionData: [], categoryData: [], restaurantData: [], selectedData: { selectedRegion: {}, selectedCategory: {} }, + regions: [], + categories: [], + restaurants: [], + selectedRegionAndCategory: { selectedRegion: {}, selectedCategory: {} }, + }; describe('fetch된 Region 데이터가 변경되었을 때', () => { it('변경된 region state가 반환된다.', () => { - const state = reducer(prevState, setRegionData({ regionData: [{ id: 1, name: '서울' }] })); - expect(state.regionData[0].name).toBe('서울'); + const state = reducer(prevState, setRegions({ regions: [{ id: 1, name: '서울' }] })); + expect(state.regions[0].name).toBe('서울'); }); }); describe('fetch된 Category 데이터가 변경되었을 때', () => { it('변경된 region state가 반환된다.', () => { - const state = reducer(prevState, setCategoryData({ categoryData: [{ id: 1, name: '한식' }] })); - expect(state.categoryData[0].name).toBe('한식'); + const state = reducer(prevState, setCategories({ categories: [{ id: 1, name: '한식' }] })); + expect(state.categories[0].name).toBe('한식'); }); }); describe('fetch된 Restaurant 데이터가 변경되었을 때', () => { it('변경된 region state가 반환된다.', () => { - const state = reducer(prevState, setRestaurantData({ restaurantData: [{ id: 1, name: '함지박식당' }] })); - expect(state.restaurantData[0].name).toBe('함지박식당'); + const state = reducer(prevState, setRestaurants({ restaurants: [{ id: 1, name: '함지박식당' }] })); + expect(state.restaurants[0].name).toBe('함지박식당'); }); }); describe('클릭한 region과 category가 변경되었을 때 ', () => { it('변경된 region state가 반환된다.', () => { - const state = reducer(prevState, updateSelectedData({ + const state = reducer(prevState, setSelectedRegionAndCategory({ selectedRegion: { id: 1, name: '서울' }, selectedCategory: { id: 1, name: '한식' }, })); - expect(state.selectedData.selectedRegion.name).toBe('서울'); - expect(state.selectedData.selectedCategory.name).toBe('한식'); + expect(state.selectedRegionAndCategory.selectedRegion.name).toBe('서울'); + expect(state.selectedRegionAndCategory.selectedCategory.name).toBe('한식'); }); }); }); From 5a892b28443bbb7e4781980e8a1482fb13e5e49b Mon Sep 17 00:00:00 2001 From: ctaaag Date: Wed, 7 Jun 2023 22:41:00 +0900 Subject: [PATCH 16/17] =?UTF-8?q?ADD:=20fixture=EB=A1=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fixtures/data.js | 4 ++++ src/App.test.jsx | 8 +++----- src/CategoriesContainer.test.jsx | 6 ++---- src/Category.test.jsx | 6 +----- src/Region.test.jsx | 9 +-------- src/RegionContainer.test.jsx | 8 ++------ src/Restaurants.test.jsx | 5 +---- src/RestaurantsContainer.jsx | 6 +++++- src/RestaurantsContainer.test.jsx | 9 +++------ src/services/api.js | 6 +++--- 10 files changed, 25 insertions(+), 42 deletions(-) create mode 100644 fixtures/data.js diff --git a/fixtures/data.js b/fixtures/data.js new file mode 100644 index 000000000..0fd8889ac --- /dev/null +++ b/fixtures/data.js @@ -0,0 +1,4 @@ +export const regions = [{ id: 1, name: '서울' }, { id: 2, name: '부산' }]; +export const categories = [{ id: 1, name: '한식' }, { id: 2, name: '양식' }]; +export const restaurants = [{ id: 1, name: '코코식당' }, { id: 2, name: '네네식당' }]; +export const selectedRegionAndCategory = { selectedRegion: { id: 1, name: '서울' }, selectedCategory: { id: 1, name: '한식' } }; diff --git a/src/App.test.jsx b/src/App.test.jsx index 03b36d150..1cea11d56 100644 --- a/src/App.test.jsx +++ b/src/App.test.jsx @@ -1,6 +1,9 @@ import { fireEvent, render } from '@testing-library/react'; import { useDispatch, useSelector } from 'react-redux'; import App from './App'; +import { + regions, categories, restaurants, selectedRegionAndCategory, +} from '../fixtures/data'; describe('App', () => { jest.mock('react-redux'); @@ -8,11 +11,6 @@ describe('App', () => { const dispatch = jest.fn(); - const regions = [{ id: 1, name: '서울' }, { id: 2, name: '부산' }]; - const categories = [{ id: 1, name: '한식' }, { id: 2, name: '양식' }]; - const restaurants = [{ id: 1, name: '코코식당' }, { id: 2, name: '네네식당' }]; - const selectedRegionAndCategory = { selectedRegion: { id: 1, name: '서울' }, selectedCategory: { id: 1, name: '한식' } }; - useDispatch.mockImplementation(() => dispatch); useSelector.mockImplementation(() => ({ regions, diff --git a/src/CategoriesContainer.test.jsx b/src/CategoriesContainer.test.jsx index c7a3cc5e0..b9a5002e0 100644 --- a/src/CategoriesContainer.test.jsx +++ b/src/CategoriesContainer.test.jsx @@ -1,5 +1,6 @@ import { render, fireEvent } from '@testing-library/react'; import { useDispatch, useSelector } from 'react-redux'; +import { categories } from '../fixtures/data'; import CategoriesContainer from './CategoriesContainer'; describe('CategoriesContainer', () => { @@ -9,10 +10,7 @@ describe('CategoriesContainer', () => { jest.mock('react-redux'); jest.mock('./services/api'); - useSelector.mockImplementation(() => ({ - categories: - [{ id: 1, name: '한식' }, { id: 2, name: '양식' }], - })); + useSelector.mockImplementation(() => ({ categories })); useDispatch.mockImplementation(() => dispatch); diff --git a/src/Category.test.jsx b/src/Category.test.jsx index ae9c190cb..9ec4ea989 100644 --- a/src/Category.test.jsx +++ b/src/Category.test.jsx @@ -1,12 +1,8 @@ import { render, fireEvent } from '@testing-library/react'; import Category from './Category'; +import { categories, selectedRegionAndCategory } from '../fixtures/data'; describe('Region', () => { - const categories = [{ id: 1, name: '한식' }, { id: 2, name: '양식' }]; - const selectedRegionAndCategory = { - selectedCategory: { id: 1, name: '한식' }, - }; - const onClick = jest.fn(); const renderCategory = () => render( diff --git a/src/Region.test.jsx b/src/Region.test.jsx index 813a94de3..fa50d1f8b 100644 --- a/src/Region.test.jsx +++ b/src/Region.test.jsx @@ -1,15 +1,8 @@ import { render, fireEvent } from '@testing-library/react'; import Region from './Region'; +import { regions, selectedRegionAndCategory } from '../fixtures/data'; describe('Region', () => { - const regions = [ - { id: 1, name: '서울' }, - { id: 2, name: '부산' }, - ]; - const selectedRegionAndCategory = { - selectedRegion: { id: 1, name: '서울' }, - }; - const onClick = jest.fn(); const renderRegion = () => render( diff --git a/src/RegionContainer.test.jsx b/src/RegionContainer.test.jsx index c0ae48e20..48dcebecd 100644 --- a/src/RegionContainer.test.jsx +++ b/src/RegionContainer.test.jsx @@ -1,5 +1,6 @@ import { fireEvent, render } from '@testing-library/react'; import { useDispatch, useSelector } from 'react-redux'; +import { regions } from '../fixtures/data'; import RegionContainer from './RegionContainer'; describe('RegionContainer', () => { @@ -7,12 +8,7 @@ describe('RegionContainer', () => { jest.mock('react-redux'); jest.mock('./services/api'); useDispatch.mockImplementation(() => dispatch); - useSelector.mockImplementation(() => ({ - regions: - [ - { id: 1, name: '서울' }, - { id: 2, name: '부산' }], - })); + useSelector.mockImplementation(() => ({ regions })); const renderRegionContainer = () => render(); diff --git a/src/Restaurants.test.jsx b/src/Restaurants.test.jsx index 130081856..400e62d20 100644 --- a/src/Restaurants.test.jsx +++ b/src/Restaurants.test.jsx @@ -1,11 +1,8 @@ import { render } from '@testing-library/react'; import Restaurants from './Restaurants'; +import { restaurants } from '../fixtures/data'; describe('RestaurantsContainer', () => { - const restaurants = [ - { id: 1, name: '코코식당' }, { id: 2, name: '네네식당' }, - ]; - const renderRestaurants = () => render(); it('식당 리스트가 보인다', () => { diff --git a/src/RestaurantsContainer.jsx b/src/RestaurantsContainer.jsx index 690031f4b..9c2b60b52 100644 --- a/src/RestaurantsContainer.jsx +++ b/src/RestaurantsContainer.jsx @@ -5,8 +5,12 @@ import Restaurants from './Restaurants'; export default function RestaurantsContainer() { const { restaurants } = useSelector((state) => state); - const { selectedRegion, selectedCategory } = useSelector((state) => state.selectedData); + const { selectedRegion, selectedCategory } = useSelector( + (state) => state.selectedRegionAndCategory, + ); + const dispatch = useDispatch(); + useEffect(() => { dispatch(loadRestaurants({ selectedRegion, selectedCategory })); }, [selectedRegion, selectedCategory]); diff --git a/src/RestaurantsContainer.test.jsx b/src/RestaurantsContainer.test.jsx index 97ae79446..d4f2952e2 100644 --- a/src/RestaurantsContainer.test.jsx +++ b/src/RestaurantsContainer.test.jsx @@ -1,5 +1,6 @@ import { render } from '@testing-library/react'; import { useDispatch, useSelector } from 'react-redux'; +import { restaurants } from '../fixtures/data'; import RestaurantsContainer from './RestaurantsContainer'; describe('RestaurantsContainer', () => { @@ -8,11 +9,7 @@ describe('RestaurantsContainer', () => { jest.mock('./services/api'); useDispatch.mockImplementation(() => dispatch); - useSelector.mockImplementation(() => ({ - restaurants: [ - { id: 1, name: '코코식당' }, { id: 2, name: '네네식당' }, - ], - })); + useSelector.mockImplementation(() => ({ restaurants })); const renderRestaurantsContainer = () => render(); @@ -20,7 +17,7 @@ describe('RestaurantsContainer', () => { const { getByText } = renderRestaurantsContainer(); expect(getByText('코코식당')).not.toBeNull(); }); - it('선택된 식당이 변경되면 해당 식당이 보인다. ', () => { + it('받아온 식당데이터가 변경되는 경우 식당이 보인다. ', () => { useSelector.mockImplementation(() => ({ restaurants: [ { id: 1, name: '바뀐식당' }, { id: 2, name: '이렇게' }, diff --git a/src/services/api.js b/src/services/api.js index 0912269a6..d015edcf8 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -1,20 +1,20 @@ export async function fetchRegions() { const url = 'https://eatgo-customer-api.ahastudio.com/regions'; const data = await fetch(url); - const result = { regionData: await data.json() }; + const result = { regions: await data.json() }; return result; } export async function fetchCategories() { const url = 'https://eatgo-customer-api.ahastudio.com/categories'; const data = await fetch(url); - const result = { categoryData: await data.json() }; + const result = { categories: await data.json() }; return result; } export async function fetchRestaurants({ regionName, categoryId }) { const url = `https://eatgo-customer-api.ahastudio.com/restaurants?region=${regionName}&category=${categoryId}`; const data = await fetch(url); - const result = { restaurantData: await data.json() }; + const result = { restaurants: await data.json() }; return result; } From 3f1ded58648b8b9e9f48227b6b4abe6eacc2f0c2 Mon Sep 17 00:00:00 2001 From: ctaaag Date: Thu, 8 Jun 2023 02:25:18 +0900 Subject: [PATCH 17/17] =?UTF-8?q?test=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BB=A4=EB=B2=84=EB=A6=AC=EC=A7=80=20=ED=96=A5?= =?UTF-8?q?=EC=83=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CategoriesContainer.jsx | 5 ++- src/CategoriesContainer.test.jsx | 22 ++++++++-- src/RegionContainer.jsx | 5 ++- src/RestaurantsContainer.jsx | 16 ++++--- src/action.js | 50 ++++++++++----------- src/action.test.js | 54 +++++++++++++++++++++++ src/reducer.js | 54 ++++++++++++++--------- src/reducer.test.js | 9 ++-- src/services/api.js | 2 +- src/services/api.test.js | 74 ++++++++++++++++++++++++++++++++ 10 files changed, 228 insertions(+), 63 deletions(-) create mode 100644 src/action.test.js create mode 100644 src/services/api.test.js diff --git a/src/CategoriesContainer.jsx b/src/CategoriesContainer.jsx index 81b2e7a40..eec89760e 100644 --- a/src/CategoriesContainer.jsx +++ b/src/CategoriesContainer.jsx @@ -5,7 +5,10 @@ import Category from './Category'; export default function CategoriesContainer() { const dispatch = useDispatch(); - const { categories, selectedRegionAndCategory } = useSelector((state) => state); + const { categories, selectedRegionAndCategory } = useSelector((state) => ({ + categories: state.categories, + selectedRegionAndCategory: state.selectedRegionAndCategory, + })); useEffect(() => { dispatch(loadCategories()); diff --git a/src/CategoriesContainer.test.jsx b/src/CategoriesContainer.test.jsx index b9a5002e0..80b728970 100644 --- a/src/CategoriesContainer.test.jsx +++ b/src/CategoriesContainer.test.jsx @@ -1,6 +1,7 @@ import { render, fireEvent } from '@testing-library/react'; import { useDispatch, useSelector } from 'react-redux'; import { categories } from '../fixtures/data'; +import { setSelectedRegionAndCategory } from './action'; import CategoriesContainer from './CategoriesContainer'; describe('CategoriesContainer', () => { @@ -16,14 +17,29 @@ describe('CategoriesContainer', () => { it('데이터 받기', () => { const { getByText } = renderCategoriesContainer(); - expect(getByText('한식')).not.toBeNull(); + expect(getByText(categories[0].name)).not.toBeNull(); }); describe('button 클릭이 되면', () => { it('클릭 된 버튼의 데이터를 state에 저장한다', () => { const { getByText } = renderCategoriesContainer(); - fireEvent.click(getByText('한식')); - expect(dispatch).toBeCalled(); + // expect(dispatch).not.toBeCalled(); + fireEvent.click(getByText(categories[0].name)); + expect(dispatch).toBeCalledWith(setSelectedRegionAndCategory({ + selectedRegion: undefined, + selectedCategory: categories[0], + })); }); }); + + // it('dispatch가 실행된다', () => { + // const category = categories[0]; + // renderCategoriesContainer(category); + + // expect(dispatch).not.toBeCalled(); + + // fireEvent.click(screen.getByText(category.name)); + + // expect(dispatch).toBeCalledWith(selectCategory(category.id)); + // }); }); diff --git a/src/RegionContainer.jsx b/src/RegionContainer.jsx index 1eeb77ff8..866070621 100644 --- a/src/RegionContainer.jsx +++ b/src/RegionContainer.jsx @@ -6,7 +6,10 @@ import Region from './Region'; export default function RegionContainer() { // 데이터 페칭하기 const dispatch = useDispatch(); - const { regions, selectedRegionAndCategory } = useSelector((state) => state); + const { regions, selectedRegionAndCategory } = useSelector((state) => ({ + regions: state.regions, + selectedRegionAndCategory: state.selectedRegionAndCategory, + })); useEffect(() => { dispatch(loadRegions()); diff --git a/src/RestaurantsContainer.jsx b/src/RestaurantsContainer.jsx index 9c2b60b52..9f89ff555 100644 --- a/src/RestaurantsContainer.jsx +++ b/src/RestaurantsContainer.jsx @@ -4,15 +4,21 @@ import { loadRestaurants } from './action'; import Restaurants from './Restaurants'; export default function RestaurantsContainer() { - const { restaurants } = useSelector((state) => state); - const { selectedRegion, selectedCategory } = useSelector( - (state) => state.selectedRegionAndCategory, - ); + const { restaurants, selectedRegion, selectedCategory } = useSelector((state) => ({ + restaurants: state.restaurants, + selectedRegion: state.selectedRegionAndCategory.selectedRegion, + selectedCategory: state.selectedRegionAndCategory.selectedCategory, + })); const dispatch = useDispatch(); useEffect(() => { - dispatch(loadRestaurants({ selectedRegion, selectedCategory })); + const isEmptyObject = (object) => Object.keys(object || {}).length === 0; + + if (isEmptyObject(selectedRegion) || isEmptyObject(selectedCategory)) { + return; + } + dispatch(loadRestaurants(selectedRegion, selectedCategory)); }, [selectedRegion, selectedCategory]); return ( diff --git a/src/action.js b/src/action.js index ccfffcb7a..ae3c5d90c 100644 --- a/src/action.js +++ b/src/action.js @@ -1,63 +1,57 @@ import { fetchRegions, fetchCategories, fetchRestaurants } from './services/api'; -export function setRegions(data) { +export function setRegions(regions) { return { type: 'SET_REGIONS', - payload: data, + payload: regions, }; } export function loadRegions() { return (async (dispatch) => { - const data = await fetchRegions(); - await dispatch(setRegions(data)); + const regions = await fetchRegions(); + await dispatch(setRegions(regions)); }); } -export function setCategories(data) { +export function setCategories(categories) { return { type: 'SET_CATEGORIES', - payload: data, + payload: categories, }; } export function loadCategories() { return ( async (dispatch) => { - const data = await fetchCategories(); - await dispatch(setCategories(data)); + const categories = await fetchCategories(); + await dispatch(setCategories(categories)); } ); } -export function setRestaurants(data) { +export function setRestaurants(restaurants) { return { type: 'SET_RESTAURANTS', - payload: data, + payload: restaurants, }; } -export function loadRestaurants({ selectedRegion, selectedCategory }) { - const isEmptyObject = (object) => Object.keys(object || {}).length === 0; - - if (isEmptyObject(selectedRegion) || isEmptyObject(selectedCategory)) { - return setRestaurants({ restaurantData: [{ id: 0, name: '불러온 레스토랑 목록이 없습니다.' }] }); - } +export function setSelectedRegionAndCategory(selectedRegionAndCategory) { + return { + type: 'SET_SELECTED_REGION_AND_CATEGORY', + payload: { selectedRegionAndCategory }, + }; +} +export function loadRestaurants(selectedRegion = '', selectedCategory = '') { return ( async (dispatch) => { - const data = await fetchRestaurants({ - regionName: selectedRegion.name, - categoryId: selectedCategory.id, - }); - await dispatch(setRestaurants(data)); + const restaurants = await fetchRestaurants( + selectedRegion.name, + selectedCategory.id, + ); + await dispatch(setRestaurants(restaurants)); } ); } - -export function setSelectedRegionAndCategory(selectedData) { - return { - type: 'SET_SELECTED_REGION_AND_CATEGORY', - payload: selectedData, - }; -} diff --git a/src/action.test.js b/src/action.test.js new file mode 100644 index 000000000..22ae1b139 --- /dev/null +++ b/src/action.test.js @@ -0,0 +1,54 @@ +import { + loadCategories, + loadRegions, + loadRestaurants, +} from './action'; + +import { categories, regions, restaurants } from '../fixtures/data'; + +jest.mock('./services/api'); + +describe('actions', () => { + const dispatch = jest.fn(); + + describe('loadCategories', () => { + beforeEach(() => { + global.fetch = jest.fn(() => Promise.resolve({ + json: () => Promise.resolve(categories), + })); + }); + + it('setCategories를 호출한다 ', async () => { + await loadCategories()(dispatch); + expect(dispatch).toBeCalled(); + }); + }); + + describe('loadRegions', () => { + beforeEach(() => { + global.fetch = jest.fn(() => Promise.resolve({ + json: () => Promise.resolve(regions), + })); + }); + + it('setRegions을 호출한다 ', async () => { + await loadRegions()(dispatch); + + expect(dispatch).toBeCalled(); + }); + }); + + describe('loadRestaurants', () => { + beforeEach(() => { + global.fetch = jest.fn(() => Promise.resolve({ + json: () => Promise.resolve(restaurants), + })); + }); + + it('setRestaurants을 호출한다 ', async () => { + await loadRestaurants()(dispatch); + + expect(dispatch).toBeCalled(); + }); + }); +}); diff --git a/src/reducer.js b/src/reducer.js index 7dbbae987..d9e4e6b33 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -6,26 +6,40 @@ const initialState = { }; const handleAction = { - SET_REGIONS: (state, { payload: { regions } }) => ({ - ...state, - regions, - }), - SET_CATEGORIES: (state, { payload: { categories } }) => ({ - ...state, - categories, - }), - SET_RESTAURANTS: (state, { payload: { restaurants } }) => ({ - ...state, - restaurants, - }), - SET_SELECTED_REGION_AND_CATEGORY: (state, { payload: { selectedRegion, selectedCategory } }) => ({ - ...state, - selectedRegionAndCategory: { - ...state.selectedRegionAndCategory, - selectedRegion, - selectedCategory, - }, - }), + SET_REGIONS: (state, action) => { + const { regions } = action.payload; + return ({ + ...state, + regions, + }); + }, + SET_CATEGORIES: (state, action) => { + const { categories } = action.payload; + return ({ + ...state, + categories, + }); + }, + + SET_RESTAURANTS: (state, action) => { + const { restaurants } = action.payload; + return ({ + ...state, + restaurants, + }); + }, + + SET_SELECTED_REGION_AND_CATEGORY: (state, action) => { + const { selectedRegion, selectedCategory } = action.payload.selectedRegionAndCategory; + return ({ + ...state, + selectedRegionAndCategory: { + ...state.selectedRegionAndCategory, + selectedRegion, + selectedCategory, + }, + }); + }, }; export default function reducer(state = initialState, action) { diff --git a/src/reducer.test.js b/src/reducer.test.js index 7981825c0..59f989649 100644 --- a/src/reducer.test.js +++ b/src/reducer.test.js @@ -2,6 +2,7 @@ import reducer from './reducer'; import { setRegions, setCategories, setRestaurants, setSelectedRegionAndCategory, } from './action'; +import { regions, categories, restaurants } from '../fixtures/data'; describe('reducer', () => { const prevState = { @@ -13,20 +14,20 @@ describe('reducer', () => { }; describe('fetch된 Region 데이터가 변경되었을 때', () => { it('변경된 region state가 반환된다.', () => { - const state = reducer(prevState, setRegions({ regions: [{ id: 1, name: '서울' }] })); + const state = reducer(prevState, setRegions({ regions })); expect(state.regions[0].name).toBe('서울'); }); }); describe('fetch된 Category 데이터가 변경되었을 때', () => { it('변경된 region state가 반환된다.', () => { - const state = reducer(prevState, setCategories({ categories: [{ id: 1, name: '한식' }] })); + const state = reducer(prevState, setCategories({ categories })); expect(state.categories[0].name).toBe('한식'); }); }); describe('fetch된 Restaurant 데이터가 변경되었을 때', () => { it('변경된 region state가 반환된다.', () => { - const state = reducer(prevState, setRestaurants({ restaurants: [{ id: 1, name: '함지박식당' }] })); - expect(state.restaurants[0].name).toBe('함지박식당'); + const state = reducer(prevState, setRestaurants({ restaurants })); + expect(state.restaurants[0].name).toBe('코코식당'); }); }); describe('클릭한 region과 category가 변경되었을 때 ', () => { diff --git a/src/services/api.js b/src/services/api.js index d015edcf8..ec51d39f6 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -12,7 +12,7 @@ export async function fetchCategories() { return result; } -export async function fetchRestaurants({ regionName, categoryId }) { +export async function fetchRestaurants(regionName, categoryId) { const url = `https://eatgo-customer-api.ahastudio.com/restaurants?region=${regionName}&category=${categoryId}`; const data = await fetch(url); const result = { restaurants: await data.json() }; diff --git a/src/services/api.test.js b/src/services/api.test.js new file mode 100644 index 000000000..b0852da8b --- /dev/null +++ b/src/services/api.test.js @@ -0,0 +1,74 @@ +import { fetchCategories, fetchRegions, fetchRestaurants } from './api'; + +import { regions, categories, restaurants } from '../../fixtures/data'; + +describe('api', () => { + describe('fetchRegions', () => { + beforeEach(() => { + global.fetch = jest.fn(() => Promise.resolve({ + json: () => Promise.resolve(regions), + })); + }); + + const regionUrl = 'https://eatgo-customer-api.ahastudio.com/regions'; + + it('지역 url을 가져온다', async () => { + await fetchRegions(); + + expect(fetch).toHaveBeenCalledWith(regionUrl); + }); + + it('지역 목록을 가져온다', async () => { + const regionsResult = await fetchRegions(); + + expect(regionsResult).toEqual({ regions }); + }); + }); + + describe('fetchCategories', () => { + beforeEach(() => { + global.fetch = jest.fn(() => Promise.resolve({ + json: () => Promise.resolve(categories), + })); + }); + + const categoriesUrl = 'https://eatgo-customer-api.ahastudio.com/categories'; + + it('카테고리 url을 가져온다', async () => { + await fetchCategories(); + + expect(fetch).toHaveBeenCalledWith(categoriesUrl); + }); + + it('카테고리 목록을 가져온다', async () => { + const categoriesResult = await fetchCategories(); + + expect(categoriesResult).toEqual({ categories }); + }); + }); + + describe('fetchRestaurants', () => { + beforeEach(() => { + global.fetch = jest.fn(() => Promise.resolve({ + json: () => Promise.resolve(restaurants), + })); + }); + + const region = regions[0]; + const category = categories[0]; + + const restaurantsUrl = `https://eatgo-customer-api.ahastudio.com/restaurants?region=${region.name}&category=${category.id}`; + + it('레스토랑 url을 가져온다', async () => { + await fetchRestaurants(region.name, category.id); + + expect(fetch).toHaveBeenCalledWith(restaurantsUrl); + }); + + it('레스토랑 목록을 가져온다', async () => { + const restaurantsResult = await fetchRestaurants({ region, category }); + + expect(restaurantsResult).toEqual({ restaurants }); + }); + }); +});