From e8544d91e2f89091909503c1e89d2038e46b6f51 Mon Sep 17 00:00:00 2001 From: saseungmin Date: Wed, 18 Nov 2020 22:34:12 +0900 Subject: [PATCH 1/5] [Feature] Add webpack style loader - npm install style-loader, css-loader, sass-loader - Apply to index.jsx globally settings index.css --- package-lock.json | 263 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 + src/index.css | 8 ++ src/index.jsx | 2 + webpack.config.js | 8 ++ 5 files changed, 284 insertions(+) create mode 100644 src/index.css diff --git a/package-lock.json b/package-lock.json index f65de66..eb999bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4334,6 +4334,62 @@ } } }, + "css-loader": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.0.1.tgz", + "integrity": "sha512-cXc2ti9V234cq7rJzFKhirb2L2iPy8ZjALeVJAozXYz9te3r4eqLSixNAbMDJSgJEQywqXzs8gonxaboeKqwiw==", + "dev": true, + "requires": { + "camelcase": "^6.2.0", + "cssesc": "^3.0.0", + "icss-utils": "^5.0.0", + "loader-utils": "^2.0.0", + "postcss": "^8.1.4", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^3.0.0", + "semver": "^7.3.2" + }, + "dependencies": { + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + } + } + }, "css-select": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", @@ -4358,6 +4414,12 @@ "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", "dev": true }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, "cssom": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", @@ -6991,6 +7053,12 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "icss-utils": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.0.0.tgz", + "integrity": "sha512-aF2Cf/CkEZrI/vsu5WI/I+akFgdbwQHVE9YRZxATrhH4PVIe6a3BIjwjEcW+z+jP/hNh+YvM3lAAn1wJQ6opSg==", + "dev": true + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -7062,6 +7130,12 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -9201,6 +9275,12 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "klona": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", + "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==", + "dev": true + }, "language-subtag-registry": { "version": "0.3.21", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz", @@ -9247,6 +9327,33 @@ "type-check": "~0.4.0" } }, + "line-column": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/line-column/-/line-column-1.0.2.tgz", + "integrity": "sha1-0lryk2tvSEkXKzEuR5LR2Ye8NKI=", + "dev": true, + "requires": { + "isarray": "^1.0.0", + "isobject": "^2.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -10576,6 +10683,79 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "postcss": { + "version": "8.1.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.1.7.tgz", + "integrity": "sha512-llCQW1Pz4MOPwbZLmOddGM9eIJ8Bh7SZ2Oj5sxZva77uVaotYDsYTch1WBTNu7fUY0fpWp0fdt7uW40D4sRiiQ==", + "dev": true, + "requires": { + "colorette": "^1.2.1", + "line-column": "^1.0.2", + "nanoid": "^3.1.16", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-selector-parser": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11772,6 +11952,49 @@ } } }, + "sass-loader": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.1.0.tgz", + "integrity": "sha512-ZCKAlczLBbFd3aGAhowpYEy69Te3Z68cg8bnHHl6WnSCvnKpbM6pQrz957HWMa8LKVuhnD9uMplmMAHwGQtHeg==", + "dev": true, + "requires": { + "klona": "^2.0.4", + "loader-utils": "^2.0.0", + "neo-async": "^2.6.2", + "schema-utils": "^3.0.0", + "semver": "^7.3.2" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + } + } + }, "saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -12843,6 +13066,40 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "style-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", + "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, "stylis": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.3.tgz", @@ -13279,6 +13536,12 @@ "set-value": "^2.0.1" } }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, "unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", diff --git a/package.json b/package.json index 421aa2c..31f3688 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@types/jest": "^26.0.15", "babel-jest": "^26.6.3", "babel-loader": "^8.2.1", + "css-loader": "^5.0.1", "eslint": "^7.13.0", "eslint-config-airbnb": "^18.2.1", "eslint-plugin-import": "^2.22.1", @@ -56,7 +57,9 @@ "jest-plugin-context": "^2.9.0", "json-server": "^0.16.3", "redux-mock-store": "^1.5.4", + "sass-loader": "^10.1.0", "start-server-and-test": "^1.11.5", + "style-loader": "^2.0.0", "webpack": "^4.43.0", "webpack-cli": "^3.3.11", "webpack-dev-server": "^3.11.0" diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..6a1bd83 --- /dev/null +++ b/src/index.css @@ -0,0 +1,8 @@ +* { + box-sizing: inherit; +} + +a { + color: inherit; + text-decoration: none; +} diff --git a/src/index.jsx b/src/index.jsx index 4820e03..be3f2f9 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -4,6 +4,8 @@ import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; +import './index.css'; + import App from './App'; import store from './reducers/store'; diff --git a/webpack.config.js b/webpack.config.js index a84b7f5..83f41fa 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -14,6 +14,14 @@ module.exports = { }, module: { rules: [ + { + test: /\.(scss|css)$/, + use: [ + 'style-loader', + 'css-loader', + 'sass-loader', + ], + }, { test: /\.jsx?$/, exclude: /node_modules/, From 2e5f09cf46c6d4686eaafb1b6661eb4a86f9945d Mon Sep 17 00:00:00 2001 From: saseungmin Date: Wed, 18 Nov 2020 22:48:14 +0900 Subject: [PATCH 2/5] [Update] H3 title tag to Link tag - Update test - Improve Link CSS --- src/App.jsx | 2 +- src/App.test.jsx | 2 +- src/components/main/StudyGroup.jsx | 17 +++++++++++++++-- src/components/main/StudyGroup.test.jsx | 9 ++++++--- src/components/main/StudyGroups.test.jsx | 9 ++++++--- .../groups/StudyGroupsContainer.test.jsx | 5 ++++- 6 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 2375271..51dd79a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -11,7 +11,7 @@ const App = () => ( - + ); diff --git a/src/App.test.jsx b/src/App.test.jsx index 7975c00..f053d7a 100644 --- a/src/App.test.jsx +++ b/src/App.test.jsx @@ -39,7 +39,7 @@ describe('App', () => { context('with path /introduce', () => { it('renders the study introduce page', () => { - const { container } = renderApp({ path: '/introduce' }); + const { container } = renderApp({ path: '/introduce/1' }); expect(container).toHaveTextContent('스터디 소개'); }); diff --git a/src/components/main/StudyGroup.jsx b/src/components/main/StudyGroup.jsx index 53017c2..45cbe12 100644 --- a/src/components/main/StudyGroup.jsx +++ b/src/components/main/StudyGroup.jsx @@ -1,18 +1,31 @@ import React from 'react'; +import { Link } from 'react-router-dom'; import styled from '@emotion/styled'; import Tags from '../common/Tags'; const StudyGroupWrapper = styled.div``; +const HeaderLink = styled(Link)` + font-size: 1.5em; + margin-bottom: 0; + margin-top: 0; + font-weight: bold; + &:hover { + color: gray; + } +`; + const StudyGroup = ({ group }) => { const { - moderatorId, title, personnel, applyEndDate, applyStartDate, tags, + id, moderatorId, title, personnel, applyEndDate, applyStartDate, tags, } = group; return ( -

{title}

+ + {title} +
{moderatorId}
{`참여 인원: ${personnel}`}
diff --git a/src/components/main/StudyGroup.test.jsx b/src/components/main/StudyGroup.test.jsx index f5c1800..88f6aec 100644 --- a/src/components/main/StudyGroup.test.jsx +++ b/src/components/main/StudyGroup.test.jsx @@ -2,13 +2,16 @@ import React from 'react'; import { render } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; import StudyGroup from './StudyGroup'; describe('StudyGroup', () => { const renderStudyGroup = ({ group }) => render(( - + + + )); it('renders study group text contents', () => { diff --git a/src/components/main/StudyGroups.test.jsx b/src/components/main/StudyGroups.test.jsx index ed405c4..bed0808 100644 --- a/src/components/main/StudyGroups.test.jsx +++ b/src/components/main/StudyGroups.test.jsx @@ -2,15 +2,18 @@ import React from 'react'; import { render } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; import StudyGroups from './StudyGroups'; import STUDY_GROUPS from '../../../fixtures/study-groups'; describe('StudyGroups', () => { const renderStudyGroups = ({ groups }) => render(( - + + + )); it('renders study group list text contents', () => { diff --git a/src/containers/groups/StudyGroupsContainer.test.jsx b/src/containers/groups/StudyGroupsContainer.test.jsx index 9ff29f8..f018597 100644 --- a/src/containers/groups/StudyGroupsContainer.test.jsx +++ b/src/containers/groups/StudyGroupsContainer.test.jsx @@ -4,6 +4,7 @@ import { render } from '@testing-library/react'; import { useDispatch, useSelector } from 'react-redux'; +import { MemoryRouter } from 'react-router-dom'; import StudyGroupsContainer from './StudyGroupsContainer'; describe('StudyGroupsContainer', () => { @@ -19,7 +20,9 @@ describe('StudyGroupsContainer', () => { }); const renderStudyGroupsContainer = () => render(( - + + + )); context('with groups', () => { From dd177c305601f797f7eb5752864563ef21590bed Mon Sep 17 00:00:00 2001 From: saseungmin Date: Wed, 18 Nov 2020 23:20:41 +0900 Subject: [PATCH 3/5] [Feature] get studygroup detail infomation - create redux dispatch and get api - Add tests --- fixtures/study-group.js | 14 ++++++++++++++ src/reducers/slice.js | 25 +++++++++++++++++++------ src/reducers/slice.test.js | 30 ++++++++++++++++++++++++++++++ src/services/__mocks__/api.js | 2 +- src/services/api.js | 5 ++++- src/services/api.test.js | 28 ++++++++++++++++++++++++++-- 6 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 fixtures/study-group.js diff --git a/fixtures/study-group.js b/fixtures/study-group.js new file mode 100644 index 0000000..f46beac --- /dev/null +++ b/fixtures/study-group.js @@ -0,0 +1,14 @@ +const studyGroup = { + id: 1, + moderatorId: 'user1', + title: '스터디를 소개합니다. 1', + personnel: 7, + contents: '우리는 이것저것 합니다.1', + tags: [ + 'JavaScript', + 'React', + 'Algorithm', + ], +}; + +export default studyGroup; diff --git a/src/reducers/slice.js b/src/reducers/slice.js index 2d38272..010334f 100644 --- a/src/reducers/slice.js +++ b/src/reducers/slice.js @@ -1,6 +1,7 @@ import { createSlice } from '@reduxjs/toolkit'; import { + getStudyGroup, getStudyGroups, } from '../services/api'; @@ -8,6 +9,7 @@ const { actions, reducer } = createSlice({ name: 'application', initialState: { groups: [], + group: null, }, reducers: { setStudyGroups(state, { payload: groups }) { @@ -16,19 +18,30 @@ const { actions, reducer } = createSlice({ groups, }; }, + setStudyGroup(state, { payload: group }) { + return { + ...state, + group, + }; + }, }, }); export const { setStudyGroups, + setStudyGroup, } = actions; -export function loadStudyGroups() { - return async (dispatch) => { - const groups = await getStudyGroups(); +export const loadStudyGroups = () => async (dispatch) => { + const groups = await getStudyGroups(); + + dispatch(setStudyGroups(groups)); +}; + +export const loadStudyGroup = (id) => async (dispatch) => { + const group = await getStudyGroup(id); - dispatch(setStudyGroups(groups)); - }; -} + dispatch(setStudyGroup(group)); +}; export default reducer; diff --git a/src/reducers/slice.test.js b/src/reducers/slice.test.js index 0a8abfd..7ce04de 100644 --- a/src/reducers/slice.test.js +++ b/src/reducers/slice.test.js @@ -6,9 +6,12 @@ import configureStore from 'redux-mock-store'; import reducer, { setStudyGroups, loadStudyGroups, + setStudyGroup, + loadStudyGroup, } from './slice'; import STUDY_GROUPS from '../../fixtures/study-groups'; +import STUDY_GROUP from '../../fixtures/study-group'; const middlewares = [thunk]; const mockStore = configureStore(middlewares); @@ -19,6 +22,7 @@ describe('reducer', () => { context('when previous state is undefined', () => { const initialState = { groups: [], + group: null, }; it('returns initialState', () => { @@ -39,6 +43,18 @@ describe('reducer', () => { expect(state.groups).toHaveLength(2); }); }); + + describe('setStudyGroup', () => { + it('get group detail contents', () => { + const initialState = { + group: null, + }; + + const state = reducer(initialState, setStudyGroup(STUDY_GROUP)); + + expect(state.group.id).toBe(1); + }); + }); }); describe('async actions', () => { @@ -57,4 +73,18 @@ describe('async actions', () => { expect(actions[0]).toEqual(setStudyGroups([])); }); }); + + describe('loadStudyGroups', () => { + beforeEach(() => { + store = mockStore({}); + }); + + it('load study group detail', async () => { + await store.dispatch(loadStudyGroup(1)); + + const actions = store.getActions(); + + expect(actions[0]).toEqual(setStudyGroup([])); + }); + }); }); diff --git a/src/services/__mocks__/api.js b/src/services/__mocks__/api.js index 30a6741..bcf0b9b 100644 --- a/src/services/__mocks__/api.js +++ b/src/services/__mocks__/api.js @@ -1,3 +1,3 @@ export const getStudyGroups = async () => []; -export const test = async () => []; +export const getStudyGroup = async () => []; diff --git a/src/services/api.js b/src/services/api.js index 425c9f0..be36463 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -5,4 +5,7 @@ export const getStudyGroups = async () => { return response.data; }; -export const test = async () => []; +export const getStudyGroup = async (id) => { + const response = await axios.get(`http://localhost:3000/groups/${id}`); + return response.data; +}; diff --git a/src/services/api.test.js b/src/services/api.test.js index 2b9e629..0f1eab2 100644 --- a/src/services/api.test.js +++ b/src/services/api.test.js @@ -1,16 +1,26 @@ import axios from 'axios'; import { + getStudyGroup, getStudyGroups, } from './api'; import STUDY_GROUPS from '../../fixtures/study-groups'; +import STUDY_GROUP from '../../fixtures/study-group'; jest.mock('axios'); describe('api', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const axiosMockResolved = (data) => { + axios.get.mockResolvedValue({ data }); + }; + describe('getStudyGroups', () => { - it('returns study groups', async () => { - axios.get.mockResolvedValue({ data: STUDY_GROUPS }); + it('returns study groups list', async () => { + axiosMockResolved(STUDY_GROUPS); await expect(getStudyGroups()).resolves.toEqual(STUDY_GROUPS); @@ -19,4 +29,18 @@ describe('api', () => { ); }); }); + + describe('getStudyGroup', () => { + const { id } = STUDY_GROUP; + + it('returns study group detail', async () => { + axiosMockResolved(STUDY_GROUP); + + await expect(getStudyGroup(id)).resolves.toEqual(STUDY_GROUP); + + expect(axios.get).toHaveBeenCalledWith( + `http://localhost:3000/groups/${id}`, + ); + }); + }); }); From 1571fc2a37d2698f77fb9f8057636ee6f35ea271 Mon Sep 17 00:00:00 2001 From: saseungmin Date: Thu, 19 Nov 2020 00:07:33 +0900 Subject: [PATCH 4/5] [Feature] Link to IntroducePage --- src/App.test.jsx | 2 + .../introduce/IntroduceContainer.jsx | 35 +++++++++++++ .../introduce/IntroduceContainer.test.jsx | 49 ++++++++++++++++++ src/pages/IntroducePage.jsx | 17 +++++-- src/pages/IntroducePage.test.jsx | 50 ++++++++++++++++--- src/reducers/slice.js | 2 + src/reducers/slice.test.js | 3 +- 7 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 src/containers/introduce/IntroduceContainer.jsx create mode 100644 src/containers/introduce/IntroduceContainer.test.jsx diff --git a/src/App.test.jsx b/src/App.test.jsx index f053d7a..009cfd4 100644 --- a/src/App.test.jsx +++ b/src/App.test.jsx @@ -7,6 +7,7 @@ import { MemoryRouter } from 'react-router-dom'; import { render } from '@testing-library/react'; import App from './App'; +import STUDY_GROUP from '../fixtures/study-group'; jest.mock('react-redux'); @@ -20,6 +21,7 @@ describe('App', () => { useSelector.mockImplementation((selector) => selector({ groups: [], + group: STUDY_GROUP, })); }); diff --git a/src/containers/introduce/IntroduceContainer.jsx b/src/containers/introduce/IntroduceContainer.jsx new file mode 100644 index 0000000..c717083 --- /dev/null +++ b/src/containers/introduce/IntroduceContainer.jsx @@ -0,0 +1,35 @@ +import React, { useEffect } from 'react'; + +import { useDispatch, useSelector } from 'react-redux'; + +import styled from '@emotion/styled'; + +import { loadStudyGroup } from '../../reducers/slice'; +import { get } from '../../../utils'; + +const IntroduceContainerWrapper = styled.div``; + +const IntroduceContainer = ({ groupId }) => { + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(loadStudyGroup(groupId)); + }, []); + + const group = useSelector(get('group')); + + if (!group) { + return ( +
로딩중..
+ ); + } + + return ( + +

{group.title}

+

{group.contents}

+
+ ); +}; + +export default IntroduceContainer; diff --git a/src/containers/introduce/IntroduceContainer.test.jsx b/src/containers/introduce/IntroduceContainer.test.jsx new file mode 100644 index 0000000..10915fc --- /dev/null +++ b/src/containers/introduce/IntroduceContainer.test.jsx @@ -0,0 +1,49 @@ +import React from 'react'; + +import { useDispatch, useSelector } from 'react-redux'; + +import { render } from '@testing-library/react'; + +import IntroduceContainer from './IntroduceContainer'; + +describe('IntroduceContainer', () => { + const dispatch = jest.fn(); + + beforeEach(() => { + dispatch.mockClear(); + + useDispatch.mockImplementation(() => dispatch); + + useSelector.mockImplementation((state) => state({ + group: { + id: 1, + moderatorId: 'user1', + title: '스터디를 소개합니다. 1', + personnel: 7, + contents: '우리는 이것저것 합니다.1', + tags: [ + 'JavaScript', + 'React', + 'Algorithm', + ], + }, + })); + }); + + const renderIntroduceContainer = ({ id }) => render( + , + ); + + it('renders study group title and contents', () => { + const { container } = renderIntroduceContainer(1); + + expect(container).toHaveTextContent('스터디를 소개합니다. 1'); + expect(container).toHaveTextContent('우리는 이것저것 합니다.1'); + }); + + it('call dispatch actions', () => { + renderIntroduceContainer(1); + + expect(dispatch).toBeCalled(); + }); +}); diff --git a/src/pages/IntroducePage.jsx b/src/pages/IntroducePage.jsx index 2a54a75..13b650e 100644 --- a/src/pages/IntroducePage.jsx +++ b/src/pages/IntroducePage.jsx @@ -1,13 +1,20 @@ import React from 'react'; +import { useParams } from 'react-router-dom'; + import styled from '@emotion/styled'; +import IntroduceContainer from '../containers/introduce/IntroduceContainer'; const IntroducePageWrapper = styled.div``; -const IntroducePage = () => ( - -

스터디 소개

-
-); +const IntroducePage = ({ params }) => { + const { id } = params || useParams(); + return ( + +

스터디 소개

+ +
+ ); +}; export default IntroducePage; diff --git a/src/pages/IntroducePage.test.jsx b/src/pages/IntroducePage.test.jsx index 6784b8c..ea45f0f 100644 --- a/src/pages/IntroducePage.test.jsx +++ b/src/pages/IntroducePage.test.jsx @@ -1,17 +1,55 @@ import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; + import { render } from '@testing-library/react'; import IntroducePage from './IntroducePage'; describe('IntroducePage', () => { - const renderIntroducePage = () => render( - , - ); + beforeEach(() => { + const dispatch = jest.fn(); + + useDispatch.mockImplementation(() => dispatch); + + useSelector.mockImplementation((state) => state({ + group: { + id: 1, + moderatorId: 'user1', + title: '스터디를 소개합니다. 1', + personnel: 7, + contents: '우리는 이것저것 합니다.1', + tags: [ + 'JavaScript', + 'React', + 'Algorithm', + ], + }, + })); + }); + + context('with params props', () => { + it('renders title', () => { + const params = { id: '1' }; + + const { container } = render( + , + ); + + expect(container).toHaveTextContent('스터디를 소개합니다. 1'); + }); + }); - it('renders Introduce Title', () => { - const { container } = renderIntroducePage(); + context('without params props', () => { + it('renders title', () => { + const { container } = render( + + + , + ); - expect(container).toHaveTextContent('스터디 소개'); + expect(container).toHaveTextContent('스터디를 소개합니다. 1'); + }); }); }); diff --git a/src/reducers/slice.js b/src/reducers/slice.js index 010334f..b671dd4 100644 --- a/src/reducers/slice.js +++ b/src/reducers/slice.js @@ -39,6 +39,8 @@ export const loadStudyGroups = () => async (dispatch) => { }; export const loadStudyGroup = (id) => async (dispatch) => { + dispatch(setStudyGroup(null)); + const group = await getStudyGroup(id); dispatch(setStudyGroup(group)); diff --git a/src/reducers/slice.test.js b/src/reducers/slice.test.js index 7ce04de..cf8fb2f 100644 --- a/src/reducers/slice.test.js +++ b/src/reducers/slice.test.js @@ -84,7 +84,8 @@ describe('async actions', () => { const actions = store.getActions(); - expect(actions[0]).toEqual(setStudyGroup([])); + expect(actions[0]).toEqual(setStudyGroup(null)); + expect(actions[1]).toEqual(setStudyGroup([])); }); }); }); From 81306b3a78123000270c8e1975c2aa4046da7b1e Mon Sep 17 00:00:00 2001 From: saseungmin Date: Thu, 19 Nov 2020 00:13:04 +0900 Subject: [PATCH 5/5] [Fix] fail covorage test - Fixed IntroduceContainer test --- .../introduce/IntroduceContainer.test.jsx | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/containers/introduce/IntroduceContainer.test.jsx b/src/containers/introduce/IntroduceContainer.test.jsx index 10915fc..9b3da5f 100644 --- a/src/containers/introduce/IntroduceContainer.test.jsx +++ b/src/containers/introduce/IntroduceContainer.test.jsx @@ -15,18 +15,7 @@ describe('IntroduceContainer', () => { useDispatch.mockImplementation(() => dispatch); useSelector.mockImplementation((state) => state({ - group: { - id: 1, - moderatorId: 'user1', - title: '스터디를 소개합니다. 1', - personnel: 7, - contents: '우리는 이것저것 합니다.1', - tags: [ - 'JavaScript', - 'React', - 'Algorithm', - ], - }, + group: given.group, })); }); @@ -34,16 +23,41 @@ describe('IntroduceContainer', () => { , ); - it('renders study group title and contents', () => { - const { container } = renderIntroduceContainer(1); + context('with group', () => { + given('group', () => ({ + id: 1, + moderatorId: 'user1', + title: '스터디를 소개합니다. 1', + personnel: 7, + contents: '우리는 이것저것 합니다.1', + tags: [ + 'JavaScript', + 'React', + 'Algorithm', + ], + })); + + it('renders study group title and contents', () => { + const { container } = renderIntroduceContainer(1); + + expect(container).toHaveTextContent('스터디를 소개합니다. 1'); + expect(container).toHaveTextContent('우리는 이것저것 합니다.1'); + }); - expect(container).toHaveTextContent('스터디를 소개합니다. 1'); - expect(container).toHaveTextContent('우리는 이것저것 합니다.1'); + it('call dispatch actions', () => { + renderIntroduceContainer(1); + + expect(dispatch).toBeCalled(); + }); }); - it('call dispatch actions', () => { - renderIntroduceContainer(1); + context('without group', () => { + given('group', () => (null)); + + it('renders "loading.." text', () => { + const { container } = renderIntroduceContainer(1); - expect(dispatch).toBeCalled(); + expect(container).toHaveTextContent('로딩중..'); + }); }); });