├── .eslintrc.js ├── .gitignore ├── README.md ├── __mocks__ └── react-redux.js ├── babel.config.js ├── fixtures └── tasks.js ├── index.html ├── jest.config.js ├── jest.setup.js ├── package-lock.json ├── package.json ├── src ├── App.jsx ├── App.test.jsx ├── actions.js ├── actions.test.js ├── index.jsx ├── reducer.js ├── reducer.test.js ├── services │ ├── __mocks__ │ │ └── api.js │ └── api.js └── store.js └── webpack.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | jest: true, 6 | }, 7 | extends: [ 8 | 'plugin:react/recommended', 9 | 'airbnb', 10 | ], 11 | globals: { 12 | Atomics: 'readonly', 13 | SharedArrayBuffer: 'readonly', 14 | Feature: 'readonly', 15 | Scenario: 'readonly', 16 | context: 'readonly', 17 | }, 18 | parserOptions: { 19 | ecmaFeatures: { 20 | jsx: true, 21 | }, 22 | ecmaVersion: 12, 23 | sourceType: 'module', 24 | }, 25 | plugins: [ 26 | 'react', 27 | ], 28 | rules: { 29 | indent: ['error', 2], 30 | 'no-trailing-spaces': 'error', 31 | curly: 'error', 32 | 'brace-style': 'error', 33 | 'no-multi-spaces': 'error', 34 | 'space-infix-ops': 'error', 35 | 'space-unary-ops': 'error', 36 | 'no-whitespace-before-property': 'error', 37 | 'func-call-spacing': 'error', 38 | 'space-before-blocks': 'error', 39 | 'keyword-spacing': ['error', { before: true, after: true }], 40 | 'comma-spacing': ['error', { before: false, after: true }], 41 | 'comma-style': ['error', 'last'], 42 | 'comma-dangle': ['error', 'always-multiline'], 43 | 'space-in-parens': ['error', 'never'], 44 | 'block-spacing': 'error', 45 | 'array-bracket-spacing': ['error', 'never'], 46 | 'object-curly-spacing': ['error', 'always'], 47 | 'key-spacing': ['error', { mode: 'strict' }], 48 | 'arrow-spacing': ['error', { before: true, after: true }], 49 | 'jsx-a11y/label-has-associated-control': ['error', { assert: 'either' }], 50 | 'react/prop-types': 'off', 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # frontend-tdd-feconf2020 2 | 3 | FEConf2020 Frontend TDD로 개발하기 예제. 4 | 5 | - 라이브 코딩 시작할 때 코드: https://github.com/1mptera/frontend-tdd-feconf2020 6 | - 완성된 코드: https://github.com/1mptera/frontend-tdd-feconf2020/tree/todo-app 7 | -------------------------------------------------------------------------------- /__mocks__/react-redux.js: -------------------------------------------------------------------------------- 1 | export const useDispatch = jest.fn(); 2 | 3 | export const useSelector = jest.fn(); 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | '@babel/preset-react', 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /fixtures/tasks.js: -------------------------------------------------------------------------------- 1 | const tasks = [ 2 | { id: 1, title: '아무 일도 하기 싫다' }, 3 | { id: 2, title: '건물 매입' }, 4 | ]; 5 | 6 | export default tasks; 7 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | setupFilesAfterEnv: [ 3 | 'jest-plugin-context/setup', 4 | './jest.setup', 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-tdd-feconf2020", 3 | "version": "1.0.0", 4 | "description": "FEConf2020 Frontend TDD로 개발하기 예제.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack serve", 8 | "test": "jest", 9 | "watch": "jest --watchAll", 10 | "lint": "eslint --fix --ext js,jsx ." 11 | }, 12 | "author": "wholemann", 13 | "dependencies": { 14 | "axios": "^0.20.0", 15 | "react": "^16.13.1", 16 | "react-dom": "^16.13.1", 17 | "react-redux": "^7.2.1", 18 | "redux": "^4.0.5", 19 | "redux-thunk": "^2.3.0" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.11.6", 23 | "@babel/preset-env": "^7.11.5", 24 | "@babel/preset-react": "^7.10.4", 25 | "@testing-library/jest-dom": "^5.11.4", 26 | "@testing-library/react": "^11.1.0", 27 | "@types/jest": "^26.0.14", 28 | "babel-jest": "^26.5.2", 29 | "babel-loader": "^8.1.0", 30 | "eslint": "^7.11.0", 31 | "jest": "^26.5.3", 32 | "jest-plugin-context": "^2.9.0", 33 | "redux-mock-store": "^1.5.4", 34 | "webpack": "^5.1.0", 35 | "webpack-cli": "^4.0.0", 36 | "webpack-dev-server": "^3.11.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function App() { 4 | return ( 5 |
6 |

To-do

7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/App.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from '@testing-library/react'; 4 | 5 | import App from './App'; 6 | -------------------------------------------------------------------------------- /src/actions.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megaptera-kr/frontend-tdd-feconf2020/7809107e39e2445a12ad3ecc1e3447299b0623a0/src/actions.js -------------------------------------------------------------------------------- /src/actions.test.js: -------------------------------------------------------------------------------- 1 | import configureStore from 'redux-mock-store'; 2 | 3 | import thunk from 'redux-thunk'; 4 | 5 | import { fetchTasks } from './services/api'; 6 | 7 | import { 8 | loadTasks, 9 | } from './actions'; 10 | 11 | jest.mock('./services/api'); 12 | 13 | const middlewares = [thunk]; 14 | const mockStore = configureStore(middlewares); 15 | 16 | describe('loadTasks', () => { 17 | it('', () => { 18 | 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import { Provider } from 'react-redux'; 5 | 6 | import App from './App'; 7 | 8 | import store from './store'; 9 | 10 | ReactDOM.render( 11 | ( 12 | 13 | 14 | 15 | ), 16 | document.getElementById('app'), 17 | ); 18 | -------------------------------------------------------------------------------- /src/reducer.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | }; 3 | 4 | export default function reducer(state = initialState, action) { 5 | } 6 | -------------------------------------------------------------------------------- /src/reducer.test.js: -------------------------------------------------------------------------------- 1 | import reducer from './reducer'; 2 | 3 | describe('reducer', () => { 4 | }); 5 | -------------------------------------------------------------------------------- /src/services/__mocks__/api.js: -------------------------------------------------------------------------------- 1 | export const fetchTasks = jest.fn(async () => []); 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/services/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const TASKS_URL = 'https://jsonplaceholder.typicode.com/todos'; 4 | 5 | export async function fetchTasks() { 6 | const { data } = await axios.get(TASKS_URL); 7 | return data; 8 | } 9 | 10 | export default {}; 11 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | 3 | import thunk from 'redux-thunk'; 4 | 5 | import reducer from './reducer'; 6 | 7 | const store = createStore(reducer, applyMiddleware(thunk)); 8 | 9 | export default store; 10 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: path.resolve(__dirname, 'src/index.jsx'), 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.jsx?$/, 9 | exclude: /node_modules/, 10 | use: 'babel-loader', 11 | }, 12 | ], 13 | }, 14 | resolve: { 15 | extensions: ['.js', '.jsx'], 16 | }, 17 | }; 18 | --------------------------------------------------------------------------------