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
new file mode 100644
index 000000000..d70cb91bf
--- /dev/null
+++ b/__mocks__/react-redux.js
@@ -0,0 +1,3 @@
+export const useDispatch = jest.fn((dispatch) => dispatch);
+
+export const useSelector = jest.fn((selector) => selector({}));
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.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/App.test.jsx b/src/App.test.jsx
new file mode 100644
index 000000000..1cea11d56
--- /dev/null
+++ b/src/App.test.jsx
@@ -0,0 +1,78 @@
+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');
+ jest.mock('./services/api');
+
+ const dispatch = jest.fn();
+
+ useDispatch.mockImplementation(() => dispatch);
+ useSelector.mockImplementation(() => ({
+ regions,
+ categories,
+ restaurants,
+ selectedRegionAndCategory,
+ }));
+
+ const renderApp = () => render();
+
+ describe('RegionContainer가 렌더링 된다.', () => {
+ it('지역정보들이 input으로 보인다.', () => {
+ const { getByText } = renderApp();
+ regions.forEach((region) => expect(getByText(new RegExp(`[${region.name}]`))).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(() => ({
+ restaurants: [
+ { id: 1, name: '바뀐식당' }, { id: 2, name: '무슨식당' },
+ ],
+ }));
+ const { getByText } = renderApp();
+ expect(getByText('바뀐식당')).not.toBeNull();
+ expect(getByText('무슨식당')).not.toBeNull();
+ });
+ });
+});
diff --git a/src/CategoriesContainer.jsx b/src/CategoriesContainer.jsx
new file mode 100644
index 000000000..eec89760e
--- /dev/null
+++ b/src/CategoriesContainer.jsx
@@ -0,0 +1,31 @@
+import { useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { loadCategories, setSelectedRegionAndCategory } from './action';
+import Category from './Category';
+
+export default function CategoriesContainer() {
+ const dispatch = useDispatch();
+ const { categories, selectedRegionAndCategory } = useSelector((state) => ({
+ categories: state.categories,
+ selectedRegionAndCategory: state.selectedRegionAndCategory,
+ }));
+
+ useEffect(() => {
+ dispatch(loadCategories());
+ }, []);
+
+ function handleClickCategory(category) {
+ dispatch(setSelectedRegionAndCategory({
+ selectedRegion: selectedRegionAndCategory?.selectedRegion,
+ selectedCategory: category,
+ }));
+ }
+
+ return (
+
+ );
+}
diff --git a/src/CategoriesContainer.test.jsx b/src/CategoriesContainer.test.jsx
new file mode 100644
index 000000000..80b728970
--- /dev/null
+++ b/src/CategoriesContainer.test.jsx
@@ -0,0 +1,45 @@
+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', () => {
+ const dispatch = jest.fn();
+ const renderCategoriesContainer = () => render();
+
+ jest.mock('react-redux');
+ jest.mock('./services/api');
+
+ useSelector.mockImplementation(() => ({ categories }));
+
+ useDispatch.mockImplementation(() => dispatch);
+
+ it('데이터 받기', () => {
+ const { getByText } = renderCategoriesContainer();
+ expect(getByText(categories[0].name)).not.toBeNull();
+ });
+
+ describe('button 클릭이 되면', () => {
+ it('클릭 된 버튼의 데이터를 state에 저장한다', () => {
+ const { getByText } = renderCategoriesContainer();
+ // 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/Category.jsx b/src/Category.jsx
new file mode 100644
index 000000000..c0aee57fa
--- /dev/null
+++ b/src/Category.jsx
@@ -0,0 +1,15 @@
+export default function Category({ categories, selectedRegionAndCategory, onClick }) {
+ return (
+ <>
+ {categories?.map((category) => (
+
+ ))}
+ >
+ );
+}
diff --git a/src/Category.test.jsx b/src/Category.test.jsx
new file mode 100644
index 000000000..9ec4ea989
--- /dev/null
+++ b/src/Category.test.jsx
@@ -0,0 +1,29 @@
+import { render, fireEvent } from '@testing-library/react';
+import Category from './Category';
+import { categories, selectedRegionAndCategory } from '../fixtures/data';
+
+describe('Region', () => {
+ 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();
+ });
+});
diff --git a/src/Region.jsx b/src/Region.jsx
new file mode 100644
index 000000000..2ed4e44b2
--- /dev/null
+++ b/src/Region.jsx
@@ -0,0 +1,15 @@
+export default function Region({ regions, selectedRegionAndCategory, onClick }) {
+ return (
+
+ {regions?.map((item) => (
+
+ ))}
+
+ );
+}
diff --git a/src/Region.test.jsx b/src/Region.test.jsx
new file mode 100644
index 000000000..fa50d1f8b
--- /dev/null
+++ b/src/Region.test.jsx
@@ -0,0 +1,29 @@
+import { render, fireEvent } from '@testing-library/react';
+import Region from './Region';
+import { regions, selectedRegionAndCategory } from '../fixtures/data';
+
+describe('Region', () => {
+ 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
new file mode 100644
index 000000000..866070621
--- /dev/null
+++ b/src/RegionContainer.jsx
@@ -0,0 +1,32 @@
+import { useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { setSelectedRegionAndCategory, loadRegions } from './action';
+import Region from './Region';
+
+export default function RegionContainer() {
+ // 데이터 페칭하기
+ const dispatch = useDispatch();
+ const { regions, selectedRegionAndCategory } = useSelector((state) => ({
+ regions: state.regions,
+ selectedRegionAndCategory: state.selectedRegionAndCategory,
+ }));
+
+ useEffect(() => {
+ dispatch(loadRegions());
+ }, []);
+
+ function handleClickButton(item) {
+ dispatch(setSelectedRegionAndCategory({
+ selectedCategory: selectedRegionAndCategory?.selectedCategory,
+ selectedRegion: item,
+ }));
+ }
+
+ return (
+
+ );
+}
diff --git a/src/RegionContainer.test.jsx b/src/RegionContainer.test.jsx
new file mode 100644
index 000000000..48dcebecd
--- /dev/null
+++ b/src/RegionContainer.test.jsx
@@ -0,0 +1,22 @@
+import { fireEvent, render } from '@testing-library/react';
+import { useDispatch, useSelector } from 'react-redux';
+import { regions } from '../fixtures/data';
+import RegionContainer from './RegionContainer';
+
+describe('RegionContainer', () => {
+ const dispatch = jest.fn();
+ jest.mock('react-redux');
+ jest.mock('./services/api');
+ useDispatch.mockImplementation(() => dispatch);
+ useSelector.mockImplementation(() => ({ regions }));
+
+ const renderRegionContainer = () => render();
+
+ it('데이터 받기', () => {
+ const { getByText } = renderRegionContainer();
+
+ expect(getByText('서울')).not.toBeNull();
+ fireEvent.click(getByText('서울'));
+ expect(dispatch).toBeCalled();
+ });
+});
diff --git a/src/Restaurants.jsx b/src/Restaurants.jsx
new file mode 100644
index 000000000..2a6413713
--- /dev/null
+++ b/src/Restaurants.jsx
@@ -0,0 +1,7 @@
+export default function Restaurants({ restaurants }) {
+ return (
+ <>
+ {restaurants?.map((restaurant) => {restaurant.name})}
+ >
+ );
+}
diff --git a/src/Restaurants.test.jsx b/src/Restaurants.test.jsx
new file mode 100644
index 000000000..400e62d20
--- /dev/null
+++ b/src/Restaurants.test.jsx
@@ -0,0 +1,13 @@
+import { render } from '@testing-library/react';
+import Restaurants from './Restaurants';
+import { restaurants } from '../fixtures/data';
+
+describe('RestaurantsContainer', () => {
+ 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
new file mode 100644
index 000000000..9f89ff555
--- /dev/null
+++ b/src/RestaurantsContainer.jsx
@@ -0,0 +1,29 @@
+import { useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { loadRestaurants } from './action';
+import Restaurants from './Restaurants';
+
+export default function RestaurantsContainer() {
+ const { restaurants, selectedRegion, selectedCategory } = useSelector((state) => ({
+ restaurants: state.restaurants,
+ selectedRegion: state.selectedRegionAndCategory.selectedRegion,
+ selectedCategory: state.selectedRegionAndCategory.selectedCategory,
+ }));
+
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ 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/RestaurantsContainer.test.jsx b/src/RestaurantsContainer.test.jsx
new file mode 100644
index 000000000..d4f2952e2
--- /dev/null
+++ b/src/RestaurantsContainer.test.jsx
@@ -0,0 +1,29 @@
+import { render } from '@testing-library/react';
+import { useDispatch, useSelector } from 'react-redux';
+import { restaurants } from '../fixtures/data';
+import RestaurantsContainer from './RestaurantsContainer';
+
+describe('RestaurantsContainer', () => {
+ const dispatch = jest.fn();
+ jest.mock('react-redux');
+ jest.mock('./services/api');
+
+ useDispatch.mockImplementation(() => dispatch);
+ useSelector.mockImplementation(() => ({ restaurants }));
+
+ const renderRestaurantsContainer = () => render();
+
+ it('식당 리스트가 보인다', () => {
+ const { getByText } = renderRestaurantsContainer();
+ expect(getByText('코코식당')).not.toBeNull();
+ });
+ it('받아온 식당데이터가 변경되는 경우 식당이 보인다. ', () => {
+ useSelector.mockImplementation(() => ({
+ restaurants: [
+ { id: 1, name: '바뀐식당' }, { id: 2, name: '이렇게' },
+ ],
+ }));
+ const { getByText } = renderRestaurantsContainer();
+ expect(getByText('바뀐식당')).not.toBeNull();
+ });
+});
diff --git a/src/action.js b/src/action.js
new file mode 100644
index 000000000..ae3c5d90c
--- /dev/null
+++ b/src/action.js
@@ -0,0 +1,57 @@
+import { fetchRegions, fetchCategories, fetchRestaurants } from './services/api';
+
+export function setRegions(regions) {
+ return {
+ type: 'SET_REGIONS',
+ payload: regions,
+ };
+}
+
+export function loadRegions() {
+ return (async (dispatch) => {
+ const regions = await fetchRegions();
+ await dispatch(setRegions(regions));
+ });
+}
+
+export function setCategories(categories) {
+ return {
+ type: 'SET_CATEGORIES',
+ payload: categories,
+ };
+}
+
+export function loadCategories() {
+ return (
+ async (dispatch) => {
+ const categories = await fetchCategories();
+ await dispatch(setCategories(categories));
+ }
+ );
+}
+
+export function setRestaurants(restaurants) {
+ return {
+ type: 'SET_RESTAURANTS',
+ payload: restaurants,
+ };
+}
+
+export function setSelectedRegionAndCategory(selectedRegionAndCategory) {
+ return {
+ type: 'SET_SELECTED_REGION_AND_CATEGORY',
+ payload: { selectedRegionAndCategory },
+ };
+}
+
+export function loadRestaurants(selectedRegion = '', selectedCategory = '') {
+ return (
+ async (dispatch) => {
+ const restaurants = await fetchRestaurants(
+ selectedRegion.name,
+ selectedCategory.id,
+ );
+ await dispatch(setRestaurants(restaurants));
+ }
+ );
+}
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/index.jsx b/src/index.jsx
index 5752f9375..fde9ee3e7 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -1,8 +1,11 @@
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..d9e4e6b33
--- /dev/null
+++ b/src/reducer.js
@@ -0,0 +1,50 @@
+const initialState = {
+ regions: [],
+ categories: [],
+ restaurants: [],
+ selectedRegionAndCategory: { selectedRegion: {}, selectedCategory: {} },
+};
+
+const handleAction = {
+ 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) {
+ if (handleAction[action.type]) {
+ return handleAction[action.type](state, action);
+ }
+ return state;
+}
diff --git a/src/reducer.test.js b/src/reducer.test.js
new file mode 100644
index 000000000..59f989649
--- /dev/null
+++ b/src/reducer.test.js
@@ -0,0 +1,44 @@
+import reducer from './reducer';
+import {
+ setRegions, setCategories, setRestaurants, setSelectedRegionAndCategory,
+} from './action';
+import { regions, categories, restaurants } from '../fixtures/data';
+
+describe('reducer', () => {
+ const prevState = {
+ regions: [],
+ categories: [],
+ restaurants: [],
+ selectedRegionAndCategory: { selectedRegion: {}, selectedCategory: {} },
+
+ };
+ describe('fetch된 Region 데이터가 변경되었을 때', () => {
+ it('변경된 region state가 반환된다.', () => {
+ const state = reducer(prevState, setRegions({ regions }));
+ expect(state.regions[0].name).toBe('서울');
+ });
+ });
+ describe('fetch된 Category 데이터가 변경되었을 때', () => {
+ it('변경된 region state가 반환된다.', () => {
+ const state = reducer(prevState, setCategories({ categories }));
+ expect(state.categories[0].name).toBe('한식');
+ });
+ });
+ describe('fetch된 Restaurant 데이터가 변경되었을 때', () => {
+ it('변경된 region state가 반환된다.', () => {
+ const state = reducer(prevState, setRestaurants({ restaurants }));
+ expect(state.restaurants[0].name).toBe('코코식당');
+ });
+ });
+ describe('클릭한 region과 category가 변경되었을 때 ', () => {
+ it('변경된 region state가 반환된다.', () => {
+ const state = reducer(prevState, setSelectedRegionAndCategory({
+ selectedRegion: { id: 1, name: '서울' },
+ selectedCategory: { id: 1, name: '한식' },
+ }));
+
+ expect(state.selectedRegionAndCategory.selectedRegion.name).toBe('서울');
+ expect(state.selectedRegionAndCategory.selectedCategory.name).toBe('한식');
+ });
+ });
+});
diff --git a/src/services/__mock__/api.js b/src/services/__mock__/api.js
new file mode 100644
index 000000000..9ad47a808
--- /dev/null
+++ b/src/services/__mock__/api.js
@@ -0,0 +1,11 @@
+export async function fetchRegions() {
+ return [];
+}
+
+export async function fetchCategories() {
+ return [];
+}
+
+export async function fetchRestaurants() {
+ return [];
+}
diff --git a/src/services/api.js b/src/services/api.js
new file mode 100644
index 000000000..ec51d39f6
--- /dev/null
+++ b/src/services/api.js
@@ -0,0 +1,20 @@
+export async function fetchRegions() {
+ const url = 'https://eatgo-customer-api.ahastudio.com/regions';
+ const data = await fetch(url);
+ 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 = { 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 = { restaurants: await data.json() };
+ return result;
+}
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 });
+ });
+ });
+});
diff --git a/src/store.js b/src/store.js
new file mode 100644
index 000000000..7a6984f8f
--- /dev/null
+++ b/src/store.js
@@ -0,0 +1,7 @@
+import { createStore, applyMiddleware } from 'redux';
+import thunk from 'redux-thunk';
+import reducer from './reducer';
+
+const store = createStore(reducer, applyMiddleware(thunk));
+
+export default store;