Skip to content

Commit

Permalink
Merge pull request #60 from yujong-lee/main
Browse files Browse the repository at this point in the history
4주차 1번째 과제
  • Loading branch information
Kihyun92 committed May 31, 2021
2 parents 7a0bfef + 72a27f6 commit 6863582
Show file tree
Hide file tree
Showing 25 changed files with 1,205 additions and 266 deletions.
5 changes: 5 additions & 0 deletions .eslintrc.js
Expand Up @@ -5,6 +5,8 @@ module.exports = {
jest: true,
},
extends: [
'plugin:testing-library/react',
'plugin:jest-dom/recommended',
'plugin:react/recommended',
'airbnb',
],
Expand All @@ -25,6 +27,7 @@ module.exports = {
Feature: 'readonly',
Scenario: 'readonly',
context: 'readonly',
given: 'readonly',
},
rules: {
indent: ['error', 2],
Expand Down Expand Up @@ -52,5 +55,7 @@ module.exports = {

'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',

'testing-library/prefer-screen-queries': 'off',
},
};
2 changes: 2 additions & 0 deletions __mock__/react-redux.js
@@ -0,0 +1,2 @@
export const useSelector = jest.fn();
export const useDispatch = jest.fn();
1 change: 1 addition & 0 deletions jest.config.js
@@ -1,5 +1,6 @@
module.exports = {
setupFilesAfterEnv: [
'given2/setup',
'jest-plugin-context/setup',
'./jest.setup',
],
Expand Down
1,012 changes: 886 additions & 126 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion package.json
Expand Up @@ -27,9 +27,12 @@
"eslint": "^7.26.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-plugin-import": "^2.23.2",
"eslint-plugin-jest-dom": "^3.9.0",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-testing-library": "^4.6.0",
"given2": "^2.1.7",
"jest": "^26.6.3",
"jest-plugin-context": "^2.9.0",
"puppeteer": "^9.1.1",
Expand All @@ -39,7 +42,10 @@
"webpack-dev-server": "^3.11.2"
},
"dependencies": {
"@testing-library/user-event": "^13.1.9",
"react": "^17.0.2",
"react-dom": "^17.0.2"
"react-dom": "^17.0.2",
"react-redux": "^7.2.4",
"redux": "^4.1.0"
}
}
48 changes: 7 additions & 41 deletions src/App.jsx
@@ -1,46 +1,12 @@
import { useState } from 'react';

import Page from './Page';
import InputContainer from './container/InputContainer';
import ListContainer from './container/ListContainer';

export default function App() {
const [state, setState] = useState({
newId: 100,
taskTitle: '',
tasks: [],
});

const { newId, taskTitle, tasks } = state;

function handleChangeTitle(event) {
setState({
...state,
taskTitle: event.target.value,
});
}

function handleClickAddTask() {
setState({
...state,
newId: newId + 1,
taskTitle: '',
tasks: [...tasks, { id: newId, title: taskTitle }],
});
}

function handleClickDeleteTask(id) {
setState({
...state,
tasks: tasks.filter((task) => task.id !== id),
});
}

return (
<Page
taskTitle={taskTitle}
onChangeTitle={handleChangeTitle}
onClickAddTask={handleClickAddTask}
tasks={tasks}
onClickDeleteTask={handleClickDeleteTask}
/>
<div>
<h1>To-do</h1>
<InputContainer />
<ListContainer />
</div>
);
}
15 changes: 8 additions & 7 deletions src/App.test.jsx
@@ -1,14 +1,15 @@
import { render } from '@testing-library/react';
import { useSelector } from 'react-redux';

import App from './App';

test('App', () => {
const { getByText } = render((
<App />
));
jest.mock('react-redux');

expect(getByText(/추가/)).not.toBeNull();
it('renders header', () => {
const initialState = { taskTitle: '', tasks: [] };
useSelector.mockImplementation((selector) => selector(initialState));

// TODO: 통합 테스트 코드 작성
// CodeceptJS => 실제 브라우저에서 사용자 테스트 실행 가능.
const { getByText } = render(<App />);

expect(getByText('To-do')).toBeInTheDocument();
});
28 changes: 0 additions & 28 deletions src/Input.test.jsx

This file was deleted.

22 changes: 0 additions & 22 deletions src/Page.jsx

This file was deleted.

31 changes: 0 additions & 31 deletions src/Page.test.jsx

This file was deleted.

28 changes: 28 additions & 0 deletions src/container/InputContainer.jsx
@@ -0,0 +1,28 @@
import { useDispatch, useSelector } from 'react-redux';

import Input from '../presentational/Input';
import {
updateTaskTitle,
addTask,
} from '../redux/actions';

export default function InputContainer() {
const taskTitle = useSelector((state) => state.taskTitle);
const dispatch = useDispatch();

function handleChangeTitle(e) {
dispatch(updateTaskTitle(e.target.value));
}

function handleClickAddTask() {
dispatch(addTask());
}

return (
<Input
value={taskTitle}
onChange={handleChangeTitle}
onClick={handleClickAddTask}
/>
);
}
39 changes: 39 additions & 0 deletions src/container/InputContainer.test.jsx
@@ -0,0 +1,39 @@
import { fireEvent, render } from '@testing-library/react';
import { useDispatch, useSelector } from 'react-redux';

import InputContainer from './InputContainer';
import {
updateTaskTitle,
addTask,
} from '../redux/actions';

jest.mock('react-redux');

describe('InputContainer', () => {
const dispatch = jest.fn();
beforeEach(() => {
dispatch.mockClear();
useDispatch.mockImplementation(() => dispatch);
});

it('updates taskTitle with input control', () => {
useSelector.mockImplementation((selector) => selector({ taskTitle: '' }));

const { getByRole } = render(<InputContainer />);

fireEvent.change(
getByRole('textbox', { name: '할 일' }),
{ target: { value: 'codesoom' } },
);

expect(dispatch).toBeCalledWith(updateTaskTitle('codesoom'));
});

it('adds task to tasks with 추가 button', () => {
const { getByRole } = render(<InputContainer />);

fireEvent.click(getByRole('button', { name: '추가' }));

expect(dispatch).toBeCalledWith(addTask());
});
});
20 changes: 20 additions & 0 deletions src/container/ListContainer.jsx
@@ -0,0 +1,20 @@
import { useDispatch, useSelector } from 'react-redux';

import List from '../presentational/List';
import { deleteTask } from '../redux/actions';

export default function ListContainer() {
const tasks = useSelector((state) => state.tasks);
const dispatch = useDispatch();

function handleClickDeleteTask(id) {
dispatch(deleteTask(id));
}

return (
<List
tasks={tasks}
onClickDelete={handleClickDeleteTask}
/>
);
}
27 changes: 27 additions & 0 deletions src/container/ListContainer.test.jsx
@@ -0,0 +1,27 @@
import { fireEvent, render, within } from '@testing-library/react';
import { useDispatch, useSelector } from 'react-redux';

import ListContainer from './ListContainer';
import { deleteTask } from '../redux/actions';

jest.mock('react-redux');

it('deletes task from tasks with 완료 button', () => {
const dispatch = jest.fn();
useDispatch.mockImplementation(() => dispatch);
useSelector.mockImplementation((selector) => selector(
{
taskTitle: '',
tasks: [{ id: 1, title: 'codesoom' }],
},
));

const { getByText } = render(<ListContainer />);

fireEvent.click(
within(getByText('codesoom'))
.getByRole('button', { name: '완료' }),
);

expect(dispatch).toBeCalledWith(deleteTask(1));
});
8 changes: 7 additions & 1 deletion src/index.jsx
@@ -1,8 +1,14 @@
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import App from './App';
import store from './redux/store';

ReactDOM.render(
<App />,
(
<Provider store={store}>
<App />
</Provider>
),
document.getElementById('app'),
);
File renamed without changes.
14 changes: 14 additions & 0 deletions src/presentational/Input.test.jsx
@@ -0,0 +1,14 @@
import { render } from '@testing-library/react';
import Input from './Input';

describe('Input', () => {
it('renders input control', () => {
const { getByPlaceholderText } = render(<Input />);
expect(getByPlaceholderText('할 일을 입력해 주세요')).toBeInTheDocument();
});

it('renders 추가 button', () => {
const { getByRole } = render(<Input />);
expect(getByRole('button', { name: '추가' })).toBeInTheDocument();
});
});
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit 6863582

Please sign in to comment.