From 39bfec5d22170a24b80f4f32afa1e3cb5c0f8f9c Mon Sep 17 00:00:00 2001 From: daadaadaah Date: Sat, 2 Jan 2021 16:06:07 +0900 Subject: [PATCH 1/8] Add E2E for UserPage --- front-end/test/user_page_test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 front-end/test/user_page_test.js diff --git a/front-end/test/user_page_test.js b/front-end/test/user_page_test.js new file mode 100644 index 0000000..90f8a50 --- /dev/null +++ b/front-end/test/user_page_test.js @@ -0,0 +1,15 @@ +const user = { + uid: 'Uid1', + githubId: 'daadaadaah', + githubProfile: 'https://avatars1.githubusercontent.com/u/60481383?s=460&v=4', +}; + +Feature('UserPage'); + +Scenario('사용자 정보가 보인다.', (I) => { + I.amOnPage('/user/daadaadaah'); + + I.see(user.githubId); + + I.waitForInvisible({ xpath: `//img[@src='${user.githubProfile}']` }); +}); From 2c7bb788446eb21d9bcfd5f0013d7e792105a290 Mon Sep 17 00:00:00 2001 From: daadaadaah Date: Sat, 2 Jan 2021 17:29:02 +0900 Subject: [PATCH 2/8] Implement api for getUser * Add getUser in `api.js` * Change users's githubId from non-unique githubId to unique githubId in `/data.js` * Add where mock for operator `=` --- fixture/data.js | 4 +- front-end/services/api/api.js | 6 +++ front-end/services/api/api.test.js | 12 +++++- .../services/firebase/__mocks__/firebase.js | 37 +++++++++++++++---- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/fixture/data.js b/fixture/data.js index 15d1886..69b9f01 100644 --- a/fixture/data.js +++ b/fixture/data.js @@ -18,12 +18,12 @@ export const users = [ }, { uid: 'Uid2', - githubId: 'daadaadaah', + githubId: 'daadaadaah2', githubProfile: 'https://avatars1.githubusercontent.com/u/60481383?s=460&v=4', }, { uid: 'Uid3', - githubId: 'daadaadaah', + githubId: 'daadaadaah3', githubProfile: 'https://avatars1.githubusercontent.com/u/60481383?s=460&v=4', }, ] diff --git a/front-end/services/api/api.js b/front-end/services/api/api.js index 4ef0b84..1fe444b 100644 --- a/front-end/services/api/api.js +++ b/front-end/services/api/api.js @@ -23,3 +23,9 @@ export async function addUser({ uid, githubId, githubProfile }) { return resaddUserInfo; } + +export async function getUser({ githubId }) { + const responses = await db.collection('user').where('githubId', '==', githubId).get(); + + return responses.docs.map((doc) => (doc.data()))[0]; +} diff --git a/front-end/services/api/api.test.js b/front-end/services/api/api.test.js index f9e9399..91967c7 100644 --- a/front-end/services/api/api.test.js +++ b/front-end/services/api/api.test.js @@ -1,4 +1,6 @@ -import { getDevLinks, getUsers, addUser } from './api'; +import { + getDevLinks, getUsers, addUser, getUser, +} from './api'; import { devLinks, users, user } from '../../../fixture/data'; @@ -30,4 +32,12 @@ describe('api', () => { expect(data).toEqual(user); }); }); + + describe('getUser', () => { + it('returns user', async () => { + const data = await getUser({ githubId: user.githubId }); + + expect(data).toEqual(user); + }); + }); }); diff --git a/front-end/services/firebase/__mocks__/firebase.js b/front-end/services/firebase/__mocks__/firebase.js index d63d22f..1ed9a29 100644 --- a/front-end/services/firebase/__mocks__/firebase.js +++ b/front-end/services/firebase/__mocks__/firebase.js @@ -50,14 +50,35 @@ const firebase = { data: () => item, })), }), - where: jest.fn().mockImplementation(() => ({ - get: jest.fn().mockResolvedValue({ - docs: collections[name].map((item) => ({ - id: item.uid, - data: () => item, - })), - }), - })), + where: jest.fn().mockImplementation((fieldName, operator, value) => { + let result = null; + + if (operator === '==') { + result = ({ + get: jest.fn().mockResolvedValue({ + docs: collections[name] + .filter((doc) => doc[fieldName] === value) + .map((doc) => ({ + id: doc.uid, + data: () => doc, + })), + }), + }); + } + + if (operator === 'in') { + result = ({ + get: jest.fn().mockResolvedValue({ + docs: collections[name].map((item) => ({ + id: item.uid, + data: () => item, + })), + }), + }); + } + + return result; + }), doc: jest.fn().mockImplementation((docName) => ({ get: jest.fn().mockResolvedValue({ id: docName, From f4bef4e87bc95c5c95b0d554293db3d067a8650f Mon Sep 17 00:00:00 2001 From: daadaadaah Date: Sat, 2 Jan 2021 18:15:44 +0900 Subject: [PATCH 3/8] Implement action for loadDevLinkerInfo * Add mock for getUser * Add setDevLinkerInfo and loadDevLinkerInfo in `slice.js` --- front-end/services/api/__mocks__/api.js | 6 ++++++ front-end/src/common/actions.test.js | 16 ++++++++++++++++ front-end/src/common/reducer.test.js | 15 +++++++++++++++ front-end/src/common/slice.js | 20 +++++++++++++++++++- 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/front-end/services/api/__mocks__/api.js b/front-end/services/api/__mocks__/api.js index 180f309..fda1105 100644 --- a/front-end/services/api/__mocks__/api.js +++ b/front-end/services/api/__mocks__/api.js @@ -1,3 +1,5 @@ +import { user } from '../../../../fixture/data'; + export async function getDevLinks() { return []; } @@ -7,3 +9,7 @@ export async function getUsers() { } export const addUser = jest.fn(); + +export async function getUser() { + return user; +} diff --git a/front-end/src/common/actions.test.js b/front-end/src/common/actions.test.js index 7a4b442..cd50a49 100644 --- a/front-end/src/common/actions.test.js +++ b/front-end/src/common/actions.test.js @@ -12,6 +12,8 @@ import { resetAccessToken, resetUserInfo, signUp, + loadDevLinkerInfo, + setDevLinkerInfo, } from './slice'; import { user } from '../../../fixture/data'; @@ -92,4 +94,18 @@ describe('actions', () => { expect(actions[0]).toEqual(setUserInfo(user)); }); }); + + describe('loadDevLinkerInfo', () => { + beforeEach(() => { + store = mockStore({}); + }); + + it('runs setUserInfo', async () => { + await store.dispatch(loadDevLinkerInfo({ devLinkerGithubId: user.githubId })); + + const actions = store.getActions(); + + expect(actions[0]).toEqual(setDevLinkerInfo(user)); + }); + }); }); diff --git a/front-end/src/common/reducer.test.js b/front-end/src/common/reducer.test.js index 5b6cf4f..412f0d4 100644 --- a/front-end/src/common/reducer.test.js +++ b/front-end/src/common/reducer.test.js @@ -4,6 +4,7 @@ import reducer, { setUserInfo, resetAccessToken, resetUserInfo, + setDevLinkerInfo, } from './slice'; import { devLink, user, accessToken } from '../../../fixture/data'; @@ -16,6 +17,7 @@ describe('reducer', () => { devLinks: [], accessToken: null, userInfo: null, + devLinkerInfo: null, }; it('returns initialState', () => { @@ -93,4 +95,17 @@ describe('reducer', () => { expect(state.userInfo).toEqual(null); }); }); + + describe('setDevLinkerInfo', () => { + it('reset devLinkerInfo', () => { + const initialState = { + accessToken, + devLinkerInfo: null, + }; + + const state = reducer(initialState, setDevLinkerInfo(user)); + + expect(state.devLinkerInfo).toEqual(user); + }); + }); }); diff --git a/front-end/src/common/slice.js b/front-end/src/common/slice.js index 56922e3..7954f2a 100644 --- a/front-end/src/common/slice.js +++ b/front-end/src/common/slice.js @@ -1,6 +1,8 @@ import { createSlice } from '@reduxjs/toolkit'; -import { getDevLinks, getUsers, addUser } from '../../services/api/api'; +import { + getDevLinks, getUsers, addUser, getUser, +} from '../../services/api/api'; import { githubOAuthLogin, @@ -19,6 +21,7 @@ const { actions, reducer } = createSlice({ devLinks: [], accessToken: null, userInfo: null, + devLinkerInfo: null, }, reducers: { setDevLinks(state, { payload: devLinks }) { @@ -51,6 +54,12 @@ const { actions, reducer } = createSlice({ userInfo: null, }; }, + setDevLinkerInfo(state, { payload: devLinkerInfo }) { + return { + ...state, + devLinkerInfo, + }; + }, }, }); @@ -60,6 +69,7 @@ export const { setUserInfo, resetAccessToken, resetUserInfo, + setDevLinkerInfo, } = actions; export function loadInitialData() { @@ -119,4 +129,12 @@ export const logout = () => async (dispatch) => { dispatch(resetUserInfo()); }; +export function loadDevLinkerInfo({ devLinkerGithubId }) { + return async (dispatch) => { + const devLinkerInfo = await getUser({ githubId: devLinkerGithubId }); + + dispatch(setDevLinkerInfo(devLinkerInfo)); + }; +} + export default reducer; From b7323b853490cfbb49eca22af1d29d929a7a3ae2 Mon Sep 17 00:00:00 2001 From: daadaadaah Date: Sat, 2 Jan 2021 20:03:27 +0900 Subject: [PATCH 4/8] Implement UserInfo component --- front-end/src/UserInfo.jsx | 63 +++++++++++++++++++++++++++++++++ front-end/src/UserInfo.test.jsx | 14 ++++++++ 2 files changed, 77 insertions(+) create mode 100644 front-end/src/UserInfo.jsx create mode 100644 front-end/src/UserInfo.test.jsx diff --git a/front-end/src/UserInfo.jsx b/front-end/src/UserInfo.jsx new file mode 100644 index 0000000..f21a6aa --- /dev/null +++ b/front-end/src/UserInfo.jsx @@ -0,0 +1,63 @@ +import React from 'react'; + +import styled from '@emotion/styled'; + +const Wrapper = styled.div({ + height: '100%', + padding: '0px 50px', + display: 'flex', + flexDirection: 'column', + margin: '0 10em', +}); +const TopWrapper = styled.div({ + display: 'flex', + flexDirection: 'row', + borderBottom: '#dcdc 1px solid', + +}); + +const TopLeftWrapper = styled.div({ + padding: '0 4em 4em 4em', +}); + +const ImgWrapper = styled.div({ + width: '12em', + height: '12em', + borderRadius: '100%', + overflow: 'hidden', + border: '#dcdc 1px solid', + '& img': { + width: '100%', + height: '100%', + objectFit: 'cover', + }, +}); + +const TopRightWrapper = styled.div({ + '& div': { + margin: '0 4em', + '& span': { + fontSize: '2em', + fontFamily: 'system-ui', + }, + }, +}); + +export default function UserInfo({ userInfo }) { + return ( + + + + + {userInfo.githubId} + + + +
+ {userInfo.githubId} +
+
+
+
+ ); +} diff --git a/front-end/src/UserInfo.test.jsx b/front-end/src/UserInfo.test.jsx new file mode 100644 index 0000000..2d95b4a --- /dev/null +++ b/front-end/src/UserInfo.test.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +import { render } from '@testing-library/react'; + +import UserInfo from './UserInfo'; + +import { user } from '../../fixture/data'; + +test('UserInfo ', () => { + const { container, getByAltText } = render(); + + expect(getByAltText(`${user.githubId}`)).toHaveAttribute('src', user.githubProfile); + expect(container).toHaveTextContent(user.githubId); +}); From 0b07dbc931427d42dd65525ebf7d896eca3b7e01 Mon Sep 17 00:00:00 2001 From: daadaadaah Date: Sun, 3 Jan 2021 00:03:27 +0900 Subject: [PATCH 5/8] Implement UserInfoContainer component --- front-end/src/UserInfoContainer.jsx | 19 ++++++++++ front-end/src/UserInfoContainer.test.jsx | 45 ++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 front-end/src/UserInfoContainer.jsx create mode 100644 front-end/src/UserInfoContainer.test.jsx diff --git a/front-end/src/UserInfoContainer.jsx b/front-end/src/UserInfoContainer.jsx new file mode 100644 index 0000000..647edc0 --- /dev/null +++ b/front-end/src/UserInfoContainer.jsx @@ -0,0 +1,19 @@ +import React from 'react'; + +import { useSelector } from 'react-redux'; + +import { get } from './common/utils'; + +import UserInfo from './UserInfo'; + +export default function UserInfoContainer() { + const devLinkerInfo = useSelector(get('devLinkerInfo')); + + if (!devLinkerInfo) { + return

로딩중....

; + } + + return ( + + ); +} diff --git a/front-end/src/UserInfoContainer.test.jsx b/front-end/src/UserInfoContainer.test.jsx new file mode 100644 index 0000000..6e25cd9 --- /dev/null +++ b/front-end/src/UserInfoContainer.test.jsx @@ -0,0 +1,45 @@ +import React from 'react'; + +import { render } from '@testing-library/react'; + +import { useSelector } from 'react-redux'; + +import UserInfoContainer from './UserInfoContainer'; + +import { user } from '../../fixture/data'; + +jest.mock('react-redux'); + +describe('', () => { + context('with devLinkerInfo', () => { + beforeEach(() => { + useSelector.mockImplementation((selector) => selector({ + devLinkerInfo: user, + })); + }); + + it('show devLinkerInfo', () => { + const { container, getByAltText } = render( + , + ); + + expect(getByAltText(`${user.githubId}`)).toHaveAttribute('src', user.githubProfile); + expect(container).toHaveTextContent(user.githubId); + }); + }); + + context('without devLinkerInfo', () => { + beforeEach(() => { + useSelector.mockImplementation((selector) => selector({ + devLinkerInfo: null, + })); + }); + + it('show loading..', () => { + const { container } = render( + , + ); + expect(container).toHaveTextContent('로딩중....'); + }); + }); +}); From 9b5ca2ded45c9687a11210f38f3872de5a928b97 Mon Sep 17 00:00:00 2001 From: daadaadaah Date: Sun, 3 Jan 2021 00:03:45 +0900 Subject: [PATCH 6/8] Implement UserPage component --- front-end/src/UserPage.jsx | 36 +++++++++++++++++++++++++++++++++ front-end/src/UserPage.test.jsx | 30 +++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 front-end/src/UserPage.jsx create mode 100644 front-end/src/UserPage.test.jsx diff --git a/front-end/src/UserPage.jsx b/front-end/src/UserPage.jsx new file mode 100644 index 0000000..e906604 --- /dev/null +++ b/front-end/src/UserPage.jsx @@ -0,0 +1,36 @@ +import React, { useEffect } from 'react'; + +import styled from '@emotion/styled'; + +import { useParams } from 'react-router-dom'; + +import { useDispatch } from 'react-redux'; + +import { + loadDevLinkerInfo, +} from './common/slice'; + +import UserInfoContainer from './UserInfoContainer'; + +const Wrapper = styled.div({ + height: '100vh', + padding: '0px 50px', + display: 'flex', + flexDirection: 'column', +}); + +export default function UserPage({ params }) { + const dispatch = useDispatch(); + + const { devLinkerId } = params || useParams(); + + useEffect(() => { + dispatch(loadDevLinkerInfo({ devLinkerGithubId: devLinkerId })); + }, []); + + return ( + + + + ); +} diff --git a/front-end/src/UserPage.test.jsx b/front-end/src/UserPage.test.jsx new file mode 100644 index 0000000..eaf2373 --- /dev/null +++ b/front-end/src/UserPage.test.jsx @@ -0,0 +1,30 @@ +import React from 'react'; + +import { render } from '@testing-library/react'; + +import { useDispatch, useSelector } from 'react-redux'; + +import UserPage from './UserPage'; + +import { user } from '../../fixture/data'; + +jest.mock('react-redux'); +jest.mock('../services/firebase/firebase.js'); + +test('UserPage ', () => { + const dispatch = jest.fn(); + + useDispatch.mockImplementation(() => dispatch); + + useSelector.mockImplementation((selector) => selector({ + devLinkerInfo: user, + })); + + expect(dispatch).not.toBeCalled(); + + render( + , + ); + + expect(dispatch).toBeCalledTimes(1); +}); From 30aeea76ed6d2279f85f1914302fd16e776daa96 Mon Sep 17 00:00:00 2001 From: daadaadaah Date: Sat, 2 Jan 2021 23:09:15 +0900 Subject: [PATCH 7/8] Use UserPage in `App.js` --- front-end/src/App.jsx | 2 ++ front-end/src/App.test.jsx | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/front-end/src/App.jsx b/front-end/src/App.jsx index 8fac40f..baf9cd9 100644 --- a/front-end/src/App.jsx +++ b/front-end/src/App.jsx @@ -7,6 +7,7 @@ import { Switch, Route } from 'react-router-dom'; import Header from './Header'; import HomePage from './HomePage'; +import UserPage from './UserPage'; import NotFoundPage from './NotFoundPage'; const Wrapper = styled.div({ @@ -20,6 +21,7 @@ export default function App() {
+ diff --git a/front-end/src/App.test.jsx b/front-end/src/App.test.jsx index 7a92f74..fc3f18b 100644 --- a/front-end/src/App.test.jsx +++ b/front-end/src/App.test.jsx @@ -4,10 +4,12 @@ import { render } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import App from './App'; +import { user } from '../../fixture/data'; + jest.mock('react-redux'); jest.mock('../services/firebase/firebase.js'); @@ -16,6 +18,10 @@ describe('App with router', () => { beforeEach(() => { useDispatch.mockImplementation(() => dispatch); + + useSelector.mockImplementation((selector) => selector({ + devLinkerInfo: user, + })); }); function renderApp({ path }) { @@ -36,13 +42,22 @@ describe('App with router', () => { }); context('with path /', () => { - it('shows loading message', () => { + it('shows devlinks', () => { const { container } = renderApp({ path: '/' }); expect(container).toHaveTextContent('로딩중'); }); }); + context('with path /user/devlinker', () => { + it('shows devlinkerInfo', () => { + const { container, getByAltText } = renderApp({ path: '/user/devlinker' }); + + expect(getByAltText(`${user.githubId}`)).toHaveAttribute('src', user.githubProfile); + expect(container).toHaveTextContent(user.githubId); + }); + }); + context('with invalid path', () => { it('renders the not found page', () => { const { container } = renderApp({ path: '/xxx' }); From a0b262cfef4ade0eceae175bbf2090b05d424887 Mon Sep 17 00:00:00 2001 From: daadaadaah Date: Sat, 2 Jan 2021 20:08:32 +0900 Subject: [PATCH 8/8] Add Link for UserPage in `Header.js` --- front-end/src/Header.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/front-end/src/Header.jsx b/front-end/src/Header.jsx index b77677e..8895b73 100644 --- a/front-end/src/Header.jsx +++ b/front-end/src/Header.jsx @@ -90,7 +90,9 @@ export default function Header() { {userInfo ? ( - {userInfo.githubId} + + {userInfo.githubId} +