From 0c31a67dc41e1405a7beb3a376aad66a4ce762f7 Mon Sep 17 00:00:00 2001 From: agaskrobot Date: Tue, 7 Apr 2020 18:37:53 +0200 Subject: [PATCH 01/17] initial commit with first working code --- aga-test/.gitignore | 23 +++++ aga-test/README.md | 68 ++++++++++++++ aga-test/package.json | 37 ++++++++ aga-test/public/favicon.ico | Bin 0 -> 3150 bytes aga-test/public/index.html | 43 +++++++++ aga-test/public/logo192.png | Bin 0 -> 5347 bytes aga-test/public/logo512.png | Bin 0 -> 9664 bytes aga-test/public/manifest.json | 25 +++++ aga-test/public/robots.txt | 3 + aga-test/src/actions/index.js | 8 ++ aga-test/src/actions/user.js | 20 ++++ aga-test/src/api/user.js | 9 ++ aga-test/src/components/User.jsx | 52 ++++++++++ aga-test/src/containers/App.jsx | 46 +++++++++ aga-test/src/containers/App.test.js | 0 aga-test/src/containers/index.js | 1 + aga-test/src/index.css | 5 + aga-test/src/index.js | 26 +++++ aga-test/src/reducers/user.js | 36 +++++++ aga-test/src/serviceWorker.js | 141 ++++++++++++++++++++++++++++ aga-test/src/setupTests.js | 5 + 21 files changed, 548 insertions(+) create mode 100644 aga-test/.gitignore create mode 100644 aga-test/README.md create mode 100644 aga-test/package.json create mode 100644 aga-test/public/favicon.ico create mode 100644 aga-test/public/index.html create mode 100644 aga-test/public/logo192.png create mode 100644 aga-test/public/logo512.png create mode 100644 aga-test/public/manifest.json create mode 100644 aga-test/public/robots.txt create mode 100644 aga-test/src/actions/index.js create mode 100644 aga-test/src/actions/user.js create mode 100644 aga-test/src/api/user.js create mode 100644 aga-test/src/components/User.jsx create mode 100644 aga-test/src/containers/App.jsx create mode 100644 aga-test/src/containers/App.test.js create mode 100644 aga-test/src/containers/index.js create mode 100644 aga-test/src/index.css create mode 100644 aga-test/src/index.js create mode 100644 aga-test/src/reducers/user.js create mode 100644 aga-test/src/serviceWorker.js create mode 100644 aga-test/src/setupTests.js diff --git a/aga-test/.gitignore b/aga-test/.gitignore new file mode 100644 index 0000000..4d29575 --- /dev/null +++ b/aga-test/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/aga-test/README.md b/aga-test/README.md new file mode 100644 index 0000000..54ef094 --- /dev/null +++ b/aga-test/README.md @@ -0,0 +1,68 @@ +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+You will also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting + +### Analyzing the Bundle Size + +This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size + +### Making a Progressive Web App + +This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app + +### Advanced Configuration + +This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration + +### Deployment + +This section has moved here: https://facebook.github.io/create-react-app/docs/deployment + +### `npm run build` fails to minify + +This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify diff --git a/aga-test/package.json b/aga-test/package.json new file mode 100644 index 0000000..ea80389 --- /dev/null +++ b/aga-test/package.json @@ -0,0 +1,37 @@ +{ + "name": "aga-test", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.5.0", + "@testing-library/user-event": "^7.2.1", + "axios": "^0.19.2", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-redux": "^7.2.0", + "react-scripts": "3.4.1", + "redux": "^4.0.5" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/aga-test/public/favicon.ico b/aga-test/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..bcd5dfd67cd0361b78123e95c2dd96031f27f743 GIT binary patch literal 3150 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 MDeX2GIKR{Qf5okjU;qFB literal 0 HcmV?d00001 diff --git a/aga-test/public/index.html b/aga-test/public/index.html new file mode 100644 index 0000000..aa069f2 --- /dev/null +++ b/aga-test/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/aga-test/public/logo192.png b/aga-test/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/aga-test/public/manifest.json b/aga-test/public/manifest.json new file mode 100644 index 0000000..080d6c7 --- /dev/null +++ b/aga-test/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/aga-test/public/robots.txt b/aga-test/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/aga-test/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/aga-test/src/actions/index.js b/aga-test/src/actions/index.js new file mode 100644 index 0000000..d17a728 --- /dev/null +++ b/aga-test/src/actions/index.js @@ -0,0 +1,8 @@ +export { + USERS_UPDATE, + USER_UPDATE, + USER_CREATE, + usersUpdate, + userUpdate, + userCreate, +} from "./user"; diff --git a/aga-test/src/actions/user.js b/aga-test/src/actions/user.js new file mode 100644 index 0000000..3a8e911 --- /dev/null +++ b/aga-test/src/actions/user.js @@ -0,0 +1,20 @@ +export const USERS_UPDATE = "users:update"; +export const USER_UPDATE = "user:update"; +export const USER_CREATE = "user:create"; +export const USER_DELETE = "user:delete"; + +export function usersUpdate(users) { + return { type: USERS_UPDATE, payload: users }; +} + +export function userUpdate(user) { + return { type: USER_UPDATE, payload: user }; +} + +export function userCreate(user) { + return { type: USER_CREATE, payload: user }; +} + +export function userDelete(user) { + return { type: USER_DELETE, payload: user }; +} \ No newline at end of file diff --git a/aga-test/src/api/user.js b/aga-test/src/api/user.js new file mode 100644 index 0000000..947eca5 --- /dev/null +++ b/aga-test/src/api/user.js @@ -0,0 +1,9 @@ +import axios from "axios/index"; + +const user = axios.create({ + baseURL: "https://jsonplaceholder.typicode.com/users", +}); + +const getUserList = () => user.get(); + +export default getUserList; diff --git a/aga-test/src/components/User.jsx b/aga-test/src/components/User.jsx new file mode 100644 index 0000000..f951716 --- /dev/null +++ b/aga-test/src/components/User.jsx @@ -0,0 +1,52 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; + +export function User({ user, onDelete, onChange }) { + const [state, setState] = useState(false); + const [email, setEmail] = useState(user.email); + + const handleChange = (e) => { + e.preventDefault(); + setState(true); + }; + + const handleSave = (e, user) => { + e.preventDefault(); + setState(false); + const updatedUser = { ...user, email: email }; + onChange(updatedUser); + }; + + const handleDelete = (e, user) => { + e.preventDefault(); + onDelete(user); + }; + + let input; + if (state === true) { + input = setEmail(e.target.value)} />; + } + return ( +
+
+
{user.name}
+
{user.email}
+ + +
+
+ {input} + +
+
+ ); +} + +User.propTypes = { + user: PropTypes.object.isRequired, + onChange: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, +}; +export default User; diff --git a/aga-test/src/containers/App.jsx b/aga-test/src/containers/App.jsx new file mode 100644 index 0000000..1b94243 --- /dev/null +++ b/aga-test/src/containers/App.jsx @@ -0,0 +1,46 @@ +import React, { useState, useEffect } from "react"; +import { useSelector, useDispatch } from "react-redux"; + +import getUserList from "../api/user"; +import { + usersUpdate, + userUpdate, + userCreate, + userDelete, +} from "../actions/user"; +import { User } from "../components/User"; + +export function App() { + // const [users, setUsers] = useState([]); + const dispatch = useDispatch(); + const users = useSelector((s) => s.users); + + useEffect(() => { + getUserList().then((u) => dispatch(usersUpdate(u.data))); + }, []); + + const handleClick = (updatedUser) => { + dispatch(userUpdate(updatedUser)); + }; + + const handleDelete = (updatedUser) => { + dispatch(userDelete(updatedUser)); + }; + + const handleAdd = (e) => { + e.preventDefault(); + const user = { name: "agusia" }; + dispatch(userCreate(user)); + }; + + return ( +
+ +
    + {users.map((user) => { + return ; + })} +
+
+ ); +} diff --git a/aga-test/src/containers/App.test.js b/aga-test/src/containers/App.test.js new file mode 100644 index 0000000..e69de29 diff --git a/aga-test/src/containers/index.js b/aga-test/src/containers/index.js new file mode 100644 index 0000000..36d0fd8 --- /dev/null +++ b/aga-test/src/containers/index.js @@ -0,0 +1 @@ +export { App } from './App'; diff --git a/aga-test/src/index.css b/aga-test/src/index.css new file mode 100644 index 0000000..b4cc725 --- /dev/null +++ b/aga-test/src/index.css @@ -0,0 +1,5 @@ +body { + margin: 0; + padding: 0; + font-family: sans-serif; +} diff --git a/aga-test/src/index.js b/aga-test/src/index.js new file mode 100644 index 0000000..a10f6db --- /dev/null +++ b/aga-test/src/index.js @@ -0,0 +1,26 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import "./index.css"; +import { App } from "./containers"; +import * as serviceWorker from "./serviceWorker"; + +import { createStore } from "redux"; +import { Provider } from "react-redux"; + +import userReducer from "./reducers/user"; + +const store = createStore(userReducer); + +ReactDOM.render( + + + + + , + document.getElementById("root") +); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: https://bit.ly/CRA-PWA +serviceWorker.unregister(); diff --git a/aga-test/src/reducers/user.js b/aga-test/src/reducers/user.js new file mode 100644 index 0000000..02b3cef --- /dev/null +++ b/aga-test/src/reducers/user.js @@ -0,0 +1,36 @@ +import { + USERS_UPDATE, + USER_UPDATE, + USER_CREATE, + USER_DELETE, +} from "../actions/user"; + +const initialState = { users: [] }; + +export const userReducer = (state = initialState, { type, payload }) => { + switch (type) { + case USERS_UPDATE: { + return { ...state, users: payload }; + } + case USER_UPDATE: { + const users = state.users.map((obj) => + payload.id === obj.id ? payload : obj + ); + return { ...state, users: users }; + } + case USER_DELETE: { + const users = state.users.filter(obj => obj.id !== payload.id); + + return { ...state, users: users }; + } + case USER_CREATE: { + const id = "_" + Math.random().toString(36).substr(2, 9); + const user = { ...payload, id: id }; + return { ...state, users: [...state.users, user] }; + } + default: + return state; + } +}; + +export default userReducer; diff --git a/aga-test/src/serviceWorker.js b/aga-test/src/serviceWorker.js new file mode 100644 index 0000000..b04b771 --- /dev/null +++ b/aga-test/src/serviceWorker.js @@ -0,0 +1,141 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on subsequent visits to a page, after all the +// existing tabs open on the page have been closed, since previously cached +// resources are updated in the background. + +// To learn more about the benefits of this model and instructions on how to +// opt-in, read https://bit.ly/CRA-PWA + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.0/8 are considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export function register(config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://bit.ly/CRA-PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl, config) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl, config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl, { + headers: { 'Service-Worker': 'script' }, + }) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready + .then(registration => { + registration.unregister(); + }) + .catch(error => { + console.error(error.message); + }); + } +} diff --git a/aga-test/src/setupTests.js b/aga-test/src/setupTests.js new file mode 100644 index 0000000..74b1a27 --- /dev/null +++ b/aga-test/src/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom/extend-expect'; From 7e94d29cbae1180c8b19d139ae05a5941ab0aa07 Mon Sep 17 00:00:00 2001 From: agaskrobot Date: Thu, 9 Apr 2020 01:05:15 +0200 Subject: [PATCH 02/17] Code works in TypeScript, Styles added --- {aga-test => aga-test-ts}/.gitignore | 0 {aga-test => aga-test-ts}/README.md | 24 --- {aga-test => aga-test-ts}/package.json | 12 +- {aga-test => aga-test-ts}/public/favicon.ico | Bin {aga-test => aga-test-ts}/public/index.html | 0 {aga-test => aga-test-ts}/public/logo192.png | Bin {aga-test => aga-test-ts}/public/logo512.png | Bin .../public/manifest.json | 0 {aga-test => aga-test-ts}/public/robots.txt | 0 aga-test-ts/src/App.test.tsx | 9 + aga-test-ts/src/api/contact.ts | 14 ++ aga-test-ts/src/api/index.ts | 6 + aga-test-ts/src/components/AddContact.tsx | 42 ++++ aga-test-ts/src/components/Contact.tsx | 83 ++++++++ aga-test-ts/src/components/Loading.tsx | 23 +++ aga-test-ts/src/components/index.ts | 3 + aga-test-ts/src/containers/App.tsx | 85 ++++++++ .../src/containers/index.ts | 0 .../src/index.js => aga-test-ts/src/index.tsx | 16 +- aga-test-ts/src/logo.svg | 7 + aga-test-ts/src/react-app-env.d.ts | 1 + .../src/serviceWorker.ts | 20 +- .../src/setupTests.ts | 0 aga-test-ts/src/store/contact/actions.ts | 24 +++ aga-test-ts/src/store/contact/index.ts | 8 + aga-test-ts/src/store/contact/reducers.ts | 32 +++ aga-test-ts/src/store/contact/types.ts | 42 ++++ aga-test-ts/src/styles/style.css | 191 ++++++++++++++++++ aga-test-ts/src/styles/style.css.map | 9 + aga-test-ts/src/styles/style.scss | 165 +++++++++++++++ aga-test-ts/tsconfig.json | 25 +++ aga-test/src/actions/index.js | 8 - aga-test/src/actions/user.js | 20 -- aga-test/src/api/user.js | 9 - aga-test/src/components/User.jsx | 52 ----- aga-test/src/containers/App.jsx | 46 ----- aga-test/src/containers/App.test.js | 0 aga-test/src/index.css | 5 - aga-test/src/reducers/user.js | 36 ---- 39 files changed, 801 insertions(+), 216 deletions(-) rename {aga-test => aga-test-ts}/.gitignore (100%) rename {aga-test => aga-test-ts}/README.md (72%) rename {aga-test => aga-test-ts}/package.json (71%) rename {aga-test => aga-test-ts}/public/favicon.ico (100%) rename {aga-test => aga-test-ts}/public/index.html (100%) rename {aga-test => aga-test-ts}/public/logo192.png (100%) rename {aga-test => aga-test-ts}/public/logo512.png (100%) rename {aga-test => aga-test-ts}/public/manifest.json (100%) rename {aga-test => aga-test-ts}/public/robots.txt (100%) create mode 100644 aga-test-ts/src/App.test.tsx create mode 100644 aga-test-ts/src/api/contact.ts create mode 100644 aga-test-ts/src/api/index.ts create mode 100644 aga-test-ts/src/components/AddContact.tsx create mode 100644 aga-test-ts/src/components/Contact.tsx create mode 100644 aga-test-ts/src/components/Loading.tsx create mode 100644 aga-test-ts/src/components/index.ts create mode 100644 aga-test-ts/src/containers/App.tsx rename aga-test/src/containers/index.js => aga-test-ts/src/containers/index.ts (100%) rename aga-test/src/index.js => aga-test-ts/src/index.tsx (71%) create mode 100644 aga-test-ts/src/logo.svg create mode 100644 aga-test-ts/src/react-app-env.d.ts rename aga-test/src/serviceWorker.js => aga-test-ts/src/serviceWorker.ts (91%) rename aga-test/src/setupTests.js => aga-test-ts/src/setupTests.ts (100%) create mode 100644 aga-test-ts/src/store/contact/actions.ts create mode 100644 aga-test-ts/src/store/contact/index.ts create mode 100644 aga-test-ts/src/store/contact/reducers.ts create mode 100644 aga-test-ts/src/store/contact/types.ts create mode 100644 aga-test-ts/src/styles/style.css create mode 100644 aga-test-ts/src/styles/style.css.map create mode 100644 aga-test-ts/src/styles/style.scss create mode 100644 aga-test-ts/tsconfig.json delete mode 100644 aga-test/src/actions/index.js delete mode 100644 aga-test/src/actions/user.js delete mode 100644 aga-test/src/api/user.js delete mode 100644 aga-test/src/components/User.jsx delete mode 100644 aga-test/src/containers/App.jsx delete mode 100644 aga-test/src/containers/App.test.js delete mode 100644 aga-test/src/index.css delete mode 100644 aga-test/src/reducers/user.js diff --git a/aga-test/.gitignore b/aga-test-ts/.gitignore similarity index 100% rename from aga-test/.gitignore rename to aga-test-ts/.gitignore diff --git a/aga-test/README.md b/aga-test-ts/README.md similarity index 72% rename from aga-test/README.md rename to aga-test-ts/README.md index 54ef094..74735dc 100644 --- a/aga-test/README.md +++ b/aga-test-ts/README.md @@ -42,27 +42,3 @@ You don’t have to ever use `eject`. The curated feature set is suitable for sm You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting - -This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting - -### Analyzing the Bundle Size - -This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size - -### Making a Progressive Web App - -This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app - -### Advanced Configuration - -This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration - -### Deployment - -This section has moved here: https://facebook.github.io/create-react-app/docs/deployment - -### `npm run build` fails to minify - -This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify diff --git a/aga-test/package.json b/aga-test-ts/package.json similarity index 71% rename from aga-test/package.json rename to aga-test-ts/package.json index ea80389..e1e74b7 100644 --- a/aga-test/package.json +++ b/aga-test-ts/package.json @@ -1,17 +1,25 @@ { - "name": "aga-test", + "name": "aga-test-ts", "version": "0.1.0", "private": true, "dependencies": { "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", + "@types/jest": "^24.9.1", + "@types/node": "^12.12.34", + "@types/react": "^16.9.32", + "@types/react-dom": "^16.9.6", + "@types/react-redux": "^7.1.7", + "@types/redux": "^3.6.0", "axios": "^0.19.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-redux": "^7.2.0", "react-scripts": "3.4.1", - "redux": "^4.0.5" + "react-spinners": "^0.8.1", + "redux": "^4.0.5", + "typescript": "^3.8.3" }, "scripts": { "start": "react-scripts start", diff --git a/aga-test/public/favicon.ico b/aga-test-ts/public/favicon.ico similarity index 100% rename from aga-test/public/favicon.ico rename to aga-test-ts/public/favicon.ico diff --git a/aga-test/public/index.html b/aga-test-ts/public/index.html similarity index 100% rename from aga-test/public/index.html rename to aga-test-ts/public/index.html diff --git a/aga-test/public/logo192.png b/aga-test-ts/public/logo192.png similarity index 100% rename from aga-test/public/logo192.png rename to aga-test-ts/public/logo192.png diff --git a/aga-test/public/logo512.png b/aga-test-ts/public/logo512.png similarity index 100% rename from aga-test/public/logo512.png rename to aga-test-ts/public/logo512.png diff --git a/aga-test/public/manifest.json b/aga-test-ts/public/manifest.json similarity index 100% rename from aga-test/public/manifest.json rename to aga-test-ts/public/manifest.json diff --git a/aga-test/public/robots.txt b/aga-test-ts/public/robots.txt similarity index 100% rename from aga-test/public/robots.txt rename to aga-test-ts/public/robots.txt diff --git a/aga-test-ts/src/App.test.tsx b/aga-test-ts/src/App.test.tsx new file mode 100644 index 0000000..09ba82c --- /dev/null +++ b/aga-test-ts/src/App.test.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { render } from "@testing-library/react"; +import { App } from "./containers/App"; + +test("renders learn react link", () => { + const { getByText } = render(); + const linkElement = getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/aga-test-ts/src/api/contact.ts b/aga-test-ts/src/api/contact.ts new file mode 100644 index 0000000..5c8a614 --- /dev/null +++ b/aga-test-ts/src/api/contact.ts @@ -0,0 +1,14 @@ +import axios from "axios/index"; +import { Contact as ContactType } from "../store/contact/types"; + +export const getContactList = () => + axios.get("https://jsonplaceholder.typicode.com/users"); + +export const deleteContact = (contact: ContactType) => + axios.delete(`https://jsonplaceholder.typicode.com/users/${contact.id}`); + +export const updateContact = (contact: ContactType) => + axios.put(`https://jsonplaceholder.typicode.com/users/${contact.id}`,contact); + +export const addContact = (contact: ContactType) => + axios.post("https://jsonplaceholder.typicode.com/users", contact); diff --git a/aga-test-ts/src/api/index.ts b/aga-test-ts/src/api/index.ts new file mode 100644 index 0000000..6641245 --- /dev/null +++ b/aga-test-ts/src/api/index.ts @@ -0,0 +1,6 @@ +export { + getContactList, + updateContact, + deleteContact, + addContact, +} from "./contact"; diff --git a/aga-test-ts/src/components/AddContact.tsx b/aga-test-ts/src/components/AddContact.tsx new file mode 100644 index 0000000..d8bccdc --- /dev/null +++ b/aga-test-ts/src/components/AddContact.tsx @@ -0,0 +1,42 @@ +import React, { useState, FC } from "react"; +import { Contact as ContactType } from "../store/contact/types"; + +export interface Props { + onAdd(contact: ContactType): void; +} + +export const AddContact: FC = ({ onAdd }) => { + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + + const handleAdd = () => { + const contact = { id: '', name: name, email: email }; + onAdd(contact); + }; + + return ( +
+ + + +
+ ); +}; diff --git a/aga-test-ts/src/components/Contact.tsx b/aga-test-ts/src/components/Contact.tsx new file mode 100644 index 0000000..36ff6d2 --- /dev/null +++ b/aga-test-ts/src/components/Contact.tsx @@ -0,0 +1,83 @@ +import React, { useState, FC } from "react"; +import { Contact as ContactType } from "../store/contact/types"; + +export interface Props { + contact: ContactType; + onDelete(contact: ContactType): void; + onChange(contact: ContactType): void; +} + +export const Contact: FC = ({ contact, onDelete, onChange }) => { + const initialEmail = contact.email; + const [state, setState] = useState(false); + const [email, setEmail] = useState(initialEmail); + + const handleChange = (e: React.MouseEvent) => { + e.preventDefault(); + setState(true); + }; + + const handleSave = ( + e: React.MouseEvent, + contact: ContactType + ) => { + e.preventDefault(); + setState(false); + const updatedContact = { ...contact, email: email }; + onChange(updatedContact); + }; + + const handleDelete = ( + e: React.MouseEvent, + contact: ContactType + ) => { + e.preventDefault(); + onDelete(contact); + }; + + let input; + if (state === true) { + input = ( + + + setEmail(e.target.value)} + /> + + + + + + ); + } + return ( + + + {contact.name} + {contact.email} + + + + + + + + {input} + + ); +}; diff --git a/aga-test-ts/src/components/Loading.tsx b/aga-test-ts/src/components/Loading.tsx new file mode 100644 index 0000000..fe65f6b --- /dev/null +++ b/aga-test-ts/src/components/Loading.tsx @@ -0,0 +1,23 @@ +import React, { FC } from "react"; +import PacmanLoader from "react-spinners/PacmanLoader"; + +export interface Props { + hidden: boolean; + children: any; +} + +export const Loading: FC = ({ children, hidden }) => { + // Children are rendered when the loader is not displayed + if (hidden === true) { + return children || null; + } + + return ( +
+
+ +
+

Loading...

+
+ ); +}; diff --git a/aga-test-ts/src/components/index.ts b/aga-test-ts/src/components/index.ts new file mode 100644 index 0000000..dd65d04 --- /dev/null +++ b/aga-test-ts/src/components/index.ts @@ -0,0 +1,3 @@ +export { AddContact } from './AddContact'; +export { Contact } from './Contact'; +export { Loading } from './Loading'; \ No newline at end of file diff --git a/aga-test-ts/src/containers/App.tsx b/aga-test-ts/src/containers/App.tsx new file mode 100644 index 0000000..ba7230c --- /dev/null +++ b/aga-test-ts/src/containers/App.tsx @@ -0,0 +1,85 @@ +import React, { useState, useEffect, FC, Suspense } from "react"; +import { Dispatch } from "redux"; +import { useSelector, useDispatch } from "react-redux"; + +import { + getContactList, + deleteContact, + addContact, + updateContact, +} from "../api"; +import { + loadAllContacts, + contactUpdate, + contactCreate, + contactDelete, +} from "../store/contact/actions"; +import { Contact, AddContact, Loading } from "../components/"; +import { + ContactActionTypes, + Contact as ContactType, +} from "../store/contact/types"; +import { RootState } from "../store/contact/index"; + +export const App: FC = () => { + const dispatch = useDispatch>(); + const contacts = useSelector((state: RootState) => state.contactReducer); + const [loading, setLoading] = useState(false); + + useEffect(() => { + setLoading(true); + getContactList() + .then((u) => dispatch(loadAllContacts(u.data))) + .finally(() => setLoading(false)); + }, []); + + const handleChange = (contact: ContactType) => { + setLoading(true); + updateContact(contact) + .then(() => dispatch(contactUpdate(contact))) + .finally(() => setLoading(false)); + }; + + const handleDelete = (contact: ContactType) => { + setLoading(true); + deleteContact(contact) + .then(() => dispatch(contactDelete(contact))) + .finally(() => setLoading(false)); + }; + + const handleAdd = (contact: ContactType) => { + setLoading(true); + addContact(contact) + .then((u) => dispatch(contactCreate(u.data))) + .finally(() => setLoading(false)); + }; + + return ( +
+ +
+ ); +}; diff --git a/aga-test/src/containers/index.js b/aga-test-ts/src/containers/index.ts similarity index 100% rename from aga-test/src/containers/index.js rename to aga-test-ts/src/containers/index.ts diff --git a/aga-test/src/index.js b/aga-test-ts/src/index.tsx similarity index 71% rename from aga-test/src/index.js rename to aga-test-ts/src/index.tsx index a10f6db..1bc4e3c 100644 --- a/aga-test/src/index.js +++ b/aga-test-ts/src/index.tsx @@ -1,22 +1,22 @@ import React from "react"; import ReactDOM from "react-dom"; -import "./index.css"; -import { App } from "./containers"; import * as serviceWorker from "./serviceWorker"; +import "./styles/style.css"; +import { App } from "./containers"; import { createStore } from "redux"; import { Provider } from "react-redux"; -import userReducer from "./reducers/user"; +import { rootReducer } from "./store/contact/index"; -const store = createStore(userReducer); +const store = createStore(rootReducer) ReactDOM.render( - - + + - - , + + , document.getElementById("root") ); diff --git a/aga-test-ts/src/logo.svg b/aga-test-ts/src/logo.svg new file mode 100644 index 0000000..6b60c10 --- /dev/null +++ b/aga-test-ts/src/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/aga-test-ts/src/react-app-env.d.ts b/aga-test-ts/src/react-app-env.d.ts new file mode 100644 index 0000000..6431bc5 --- /dev/null +++ b/aga-test-ts/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/aga-test/src/serviceWorker.js b/aga-test-ts/src/serviceWorker.ts similarity index 91% rename from aga-test/src/serviceWorker.js rename to aga-test-ts/src/serviceWorker.ts index b04b771..f656427 100644 --- a/aga-test/src/serviceWorker.js +++ b/aga-test-ts/src/serviceWorker.ts @@ -2,7 +2,7 @@ // register() is not called by default. // This lets the app load faster on subsequent visits in production, and gives -// it offline capabilities. However, it also means that developers (and users) +// it offline capabilities. However, it also means that developers (and contacts) // will only see deployed updates on subsequent visits to a page, after all the // existing tabs open on the page have been closed, since previously cached // resources are updated in the background. @@ -20,10 +20,18 @@ const isLocalhost = Boolean( ) ); -export function register(config) { +type Config = { + onSuccess?: (registration: ServiceWorkerRegistration) => void; + onUpdate?: (registration: ServiceWorkerRegistration) => void; +}; + +export function register(config?: Config) { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + const publicUrl = new URL( + process.env.PUBLIC_URL, + window.location.href + ); if (publicUrl.origin !== window.location.origin) { // Our service worker won't work if PUBLIC_URL is on a different origin // from what our page is served on. This might happen if a CDN is used to @@ -54,7 +62,7 @@ export function register(config) { } } -function registerValidSW(swUrl, config) { +function registerValidSW(swUrl: string, config?: Config) { navigator.serviceWorker .register(swUrl) .then(registration => { @@ -98,10 +106,10 @@ function registerValidSW(swUrl, config) { }); } -function checkValidServiceWorker(swUrl, config) { +function checkValidServiceWorker(swUrl: string, config?: Config) { // Check if the service worker can be found. If it can't reload the page. fetch(swUrl, { - headers: { 'Service-Worker': 'script' }, + headers: { 'Service-Worker': 'script' } }) .then(response => { // Ensure service worker exists, and that we really are getting a JS file. diff --git a/aga-test/src/setupTests.js b/aga-test-ts/src/setupTests.ts similarity index 100% rename from aga-test/src/setupTests.js rename to aga-test-ts/src/setupTests.ts diff --git a/aga-test-ts/src/store/contact/actions.ts b/aga-test-ts/src/store/contact/actions.ts new file mode 100644 index 0000000..7fa47a8 --- /dev/null +++ b/aga-test-ts/src/store/contact/actions.ts @@ -0,0 +1,24 @@ +import { + Contact, + USERS_UPDATE, + USER_UPDATE, + USER_CREATE, + USER_DELETE, + ContactActionTypes, +} from "./types"; + +export function loadAllContacts(contacts: Array): ContactActionTypes { + return { type: USERS_UPDATE, payload: contacts }; +} + +export function contactUpdate(contact: Contact): ContactActionTypes { + return { type: USER_UPDATE, payload: { contact } }; +} + +export function contactCreate(contact: Contact): ContactActionTypes { + return { type: USER_CREATE, payload: { contact } }; +} + +export function contactDelete(contact: Contact): ContactActionTypes { + return { type: USER_DELETE, payload: { contact } }; +} diff --git a/aga-test-ts/src/store/contact/index.ts b/aga-test-ts/src/store/contact/index.ts new file mode 100644 index 0000000..007e606 --- /dev/null +++ b/aga-test-ts/src/store/contact/index.ts @@ -0,0 +1,8 @@ +import { combineReducers } from "redux"; +import { contactReducer } from "./reducers"; + +export const rootReducer = combineReducers({ contactReducer }); + +export type RootState = ReturnType; + +export default rootReducer \ No newline at end of file diff --git a/aga-test-ts/src/store/contact/reducers.ts b/aga-test-ts/src/store/contact/reducers.ts new file mode 100644 index 0000000..d494b8b --- /dev/null +++ b/aga-test-ts/src/store/contact/reducers.ts @@ -0,0 +1,32 @@ +import { + Contact, + ContactActionTypes, + USERS_UPDATE, + USER_UPDATE, + USER_CREATE, + USER_DELETE, +} from "./types"; + +export function contactReducer(state: Array = [], action: ContactActionTypes) { + switch (action.type) { + case USERS_UPDATE: { + return action.payload; + } + case USER_UPDATE: { + const contacts = state.map((obj) => + action.payload.contact.id === obj.id ? action.payload.contact : obj + ); + return contacts; + } + case USER_DELETE: { + const contacts = state.filter((obj) => obj.id !== action.payload.contact.id); + + return contacts; + } + case USER_CREATE: { + return [...state, action.payload.contact]; + } + default: + return state; + } +} diff --git a/aga-test-ts/src/store/contact/types.ts b/aga-test-ts/src/store/contact/types.ts new file mode 100644 index 0000000..25515d1 --- /dev/null +++ b/aga-test-ts/src/store/contact/types.ts @@ -0,0 +1,42 @@ +export interface Contact { + readonly id: string; + readonly name: string; + readonly email: string; +} + +export const USERS_UPDATE = "USERS_UPDATE"; +export const USER_UPDATE = "USER_UPDATE"; +export const USER_CREATE = "USER_CREATE"; +export const USER_DELETE = "USER_DELETE"; + +interface loadAllContactsAction { + type: typeof USERS_UPDATE; + payload: Array; +} + +interface contactUpdateAction { + type: typeof USER_UPDATE; + payload: { + contact: Contact; + }; +} + +interface contactCreateAction { + type: typeof USER_CREATE; + payload: { + contact: Contact; + }; +} + +interface contactDeleteAction { + type: typeof USER_DELETE; + payload: { + contact: Contact; + }; +} + +export type ContactActionTypes = + | loadAllContactsAction + | contactUpdateAction + | contactCreateAction + | contactDeleteAction; diff --git a/aga-test-ts/src/styles/style.css b/aga-test-ts/src/styles/style.css new file mode 100644 index 0000000..2f2d4a3 --- /dev/null +++ b/aga-test-ts/src/styles/style.css @@ -0,0 +1,191 @@ +@import url("https://fonts.googleapis.com/css2?family=Varela+Round&display=swap"); +html, +body { + width: 100%; + height: 100%; +} + +body { + font-family: "Varela Round", sans-serif; + color: #2e2e2e; + font-size: 18px; + line-height: 1.5; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + background: #b3b3ff; + background: linear-gradient(45deg, #b3b3ff, #8080ff); + height: 100%; + margin: 0; + padding: 0; +} + +.contacts-app { + padding-top: 20px; + min-width: 800px; + max-width: 80%; + min-height: 480px; + max-height: 60%; + background: #fafafa; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + margin: 0; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + border-radius: 5px; + -webkit-box-shadow: 0 0 5px rgba(25, 25, 25, 0.25); + box-shadow: 0 0 5px rgba(25, 25, 25, 0.25); + padding: 3rem; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} + +.contacts-title { + margin-bottom: auto; + margin-top: 0; + line-height: 1; +} + +.contact-loading { + margin: 0; + border: 0; + padding: 0; + display: inline-block; + vertical-align: middle; + white-space: normal; + background: none; + outline: none; +} + +.contact-loading__title { + margin-top: 20px; +} + +.contact-loading__spinner { + height: 110px; + margin: 20px; +} + +.add-contact { + width: 100%; + position: relative; +} + +.add-contact__btn { + font-family: "Varela Round", sans-serif; + width: 70px; + background-color: #36b03c; + font-size: 20px; + color: white; + padding: 15px 10px; + margin-top: 20px; + border: none; + border-radius: 5px; + cursor: pointer; + letter-spacing: 2px; + outline: none; +} + +.add-contact__btn:focus { + -webkit-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); +} + +.add-contact__btn:hover { + background-color: #37b93d; +} + +.add-contact__input { + width: 20%; + background-color: #efefef; + color: #666; + border: 2px solid #ddd; + border-radius: 5px; + font-size: 20px; + padding: 10px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + outline: none; + margin: 10px; +} + +.add-contact__input:focus { + -webkit-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + border: 2px solid #a5cda5; + background-color: #e9f3e9; + color: #428c42; +} + +.contact-row { + padding-top: 7px; +} + +.contact-row__input { + background-color: #efefef; + color: #666; + width: 200px; + border: 2px solid #ddd; + border-radius: 5px; + font-size: 15px; + padding: 3px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + outline: none; +} + +.contact-row__input:focus { + -webkit-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + border: 2px solid #eec596; + background-color: #f0d6b8; + color: #8c7242; +} + +.contact-row__btn { + margin-left: auto; + font-family: "Varela Round", sans-serif; + width: auto; + background-color: #e0841a; + font-size: 15px; + color: white; + padding: 5px 5px; + border: none; + border-radius: 5px; + cursor: pointer; + letter-spacing: 2px; + outline: none; +} + +.contact-row__btn.delete { + background-color: #d63619; +} + +.contact-row__btn.delete:hover { + background-color: #ec634a; +} + +.contact-row__btn:focus { + -webkit-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); +} + +.contact-row__btn:hover { + background-color: #faa13b; +} +/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/aga-test-ts/src/styles/style.css.map b/aga-test-ts/src/styles/style.css.map new file mode 100644 index 0000000..0b1be09 --- /dev/null +++ b/aga-test-ts/src/styles/style.css.map @@ -0,0 +1,9 @@ +{ + "version": 3, + "mappings": "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", + "sources": [ + "style.scss" + ], + "names": [], + "file": "style.css" +} \ No newline at end of file diff --git a/aga-test-ts/src/styles/style.scss b/aga-test-ts/src/styles/style.scss new file mode 100644 index 0000000..6b8bcd8 --- /dev/null +++ b/aga-test-ts/src/styles/style.scss @@ -0,0 +1,165 @@ +@import url("https://fonts.googleapis.com/css2?family=Varela+Round&display=swap"); + +html, +body { + width: 100%; + height: 100%; +} + +body { + font-family: "Varela Round", sans-serif; + color: #2e2e2e; + font-size: 18px; + line-height: 1.5; + align-items: center; + justify-content: center; + background: #b3b3ff; + background: linear-gradient(45deg, #b3b3ff, #8080ff); + height: 100%; + margin: 0; + padding: 0; +} + +.contacts-app { + padding-top: 20px; + min-width: 800px; + max-width: 80%; + min-height: 480px; + max-height: 60%; + background: #fafafa; + display: flex; + margin: 0; + flex-direction: column; + align-items: center; + justify-content: center; + border-radius: 5px; + box-shadow: 0 0 5px rgba(25, 25, 25, 0.25); + padding: 3rem; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.contacts-title { + margin-bottom: auto; + margin-top: 0; + line-height: 1; +} + +.contact-loading { + margin: 0; + border: 0; + padding: 0; + display: inline-block; + vertical-align: middle; + white-space: normal; + background: none; + outline: none; + &__title { + margin-top: 20px; + } + &__spinner { + height: 110px; + margin: 20px; + } +} + +.add-contact { + width: 100%; + position: relative; + &__btn { + font-family: "Varela Round", sans-serif; + width: 70px; + background-color: #36b03c; + font-size: 20px; + color: white; + padding: 15px 10px; + margin-top: 20px; + border: none; + border-radius: 5px; + cursor: pointer; + letter-spacing: 2px; + outline: none; + &:focus { + -webkit-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + -moz-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + } + &:hover { + background-color: #37b93d; + } + } + &__input { + width: 20%; + background-color: #efefef; + color: #666; + border: 2px solid #ddd; + border-radius: 5px; + font-size: 20px; + padding: 10px; + box-sizing: border-box; + outline: none; + margin: 10px; + &:focus { + -webkit-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + -moz-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + border: 2px solid #a5cda5; + background-color: #e9f3e9; + color: #428c42; + } + } +} + +.contact-row { + padding-top: 7px;; + &__input { + background-color: #efefef; + color: #666; + width: 200px; + border: 2px solid #ddd; + border-radius: 5px; + font-size: 15px; + padding: 3px; + box-sizing: border-box; + outline: none; + &:focus { + -webkit-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + -moz-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + border: 2px solid #eec596; + background-color: #f0d6b8; + color: #8c7242; + } + } + &__btn { + margin-left: auto; + font-family: "Varela Round", sans-serif; + width: auto; + background-color: #e0841a; + font-size: 15px; + color: white; + padding: 5px 5px; + border: none; + border-radius: 5px; + cursor: pointer; + letter-spacing: 2px; + outline: none; + &.delete{ + background-color: #d63619; + &:hover { + background-color: #ec634a; + } + } + + &:focus { + -webkit-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + -moz-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + } + &:hover { + background-color: #faa13b; + } + } +} diff --git a/aga-test-ts/tsconfig.json b/aga-test-ts/tsconfig.json new file mode 100644 index 0000000..f2850b7 --- /dev/null +++ b/aga-test-ts/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": [ + "src" + ] +} diff --git a/aga-test/src/actions/index.js b/aga-test/src/actions/index.js deleted file mode 100644 index d17a728..0000000 --- a/aga-test/src/actions/index.js +++ /dev/null @@ -1,8 +0,0 @@ -export { - USERS_UPDATE, - USER_UPDATE, - USER_CREATE, - usersUpdate, - userUpdate, - userCreate, -} from "./user"; diff --git a/aga-test/src/actions/user.js b/aga-test/src/actions/user.js deleted file mode 100644 index 3a8e911..0000000 --- a/aga-test/src/actions/user.js +++ /dev/null @@ -1,20 +0,0 @@ -export const USERS_UPDATE = "users:update"; -export const USER_UPDATE = "user:update"; -export const USER_CREATE = "user:create"; -export const USER_DELETE = "user:delete"; - -export function usersUpdate(users) { - return { type: USERS_UPDATE, payload: users }; -} - -export function userUpdate(user) { - return { type: USER_UPDATE, payload: user }; -} - -export function userCreate(user) { - return { type: USER_CREATE, payload: user }; -} - -export function userDelete(user) { - return { type: USER_DELETE, payload: user }; -} \ No newline at end of file diff --git a/aga-test/src/api/user.js b/aga-test/src/api/user.js deleted file mode 100644 index 947eca5..0000000 --- a/aga-test/src/api/user.js +++ /dev/null @@ -1,9 +0,0 @@ -import axios from "axios/index"; - -const user = axios.create({ - baseURL: "https://jsonplaceholder.typicode.com/users", -}); - -const getUserList = () => user.get(); - -export default getUserList; diff --git a/aga-test/src/components/User.jsx b/aga-test/src/components/User.jsx deleted file mode 100644 index f951716..0000000 --- a/aga-test/src/components/User.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import React, { useState } from "react"; -import PropTypes from "prop-types"; - -export function User({ user, onDelete, onChange }) { - const [state, setState] = useState(false); - const [email, setEmail] = useState(user.email); - - const handleChange = (e) => { - e.preventDefault(); - setState(true); - }; - - const handleSave = (e, user) => { - e.preventDefault(); - setState(false); - const updatedUser = { ...user, email: email }; - onChange(updatedUser); - }; - - const handleDelete = (e, user) => { - e.preventDefault(); - onDelete(user); - }; - - let input; - if (state === true) { - input = setEmail(e.target.value)} />; - } - return ( -
-
-
{user.name}
-
{user.email}
- - -
-
- {input} - -
-
- ); -} - -User.propTypes = { - user: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, - onDelete: PropTypes.func.isRequired, -}; -export default User; diff --git a/aga-test/src/containers/App.jsx b/aga-test/src/containers/App.jsx deleted file mode 100644 index 1b94243..0000000 --- a/aga-test/src/containers/App.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { useSelector, useDispatch } from "react-redux"; - -import getUserList from "../api/user"; -import { - usersUpdate, - userUpdate, - userCreate, - userDelete, -} from "../actions/user"; -import { User } from "../components/User"; - -export function App() { - // const [users, setUsers] = useState([]); - const dispatch = useDispatch(); - const users = useSelector((s) => s.users); - - useEffect(() => { - getUserList().then((u) => dispatch(usersUpdate(u.data))); - }, []); - - const handleClick = (updatedUser) => { - dispatch(userUpdate(updatedUser)); - }; - - const handleDelete = (updatedUser) => { - dispatch(userDelete(updatedUser)); - }; - - const handleAdd = (e) => { - e.preventDefault(); - const user = { name: "agusia" }; - dispatch(userCreate(user)); - }; - - return ( -
- -
    - {users.map((user) => { - return ; - })} -
-
- ); -} diff --git a/aga-test/src/containers/App.test.js b/aga-test/src/containers/App.test.js deleted file mode 100644 index e69de29..0000000 diff --git a/aga-test/src/index.css b/aga-test/src/index.css deleted file mode 100644 index b4cc725..0000000 --- a/aga-test/src/index.css +++ /dev/null @@ -1,5 +0,0 @@ -body { - margin: 0; - padding: 0; - font-family: sans-serif; -} diff --git a/aga-test/src/reducers/user.js b/aga-test/src/reducers/user.js deleted file mode 100644 index 02b3cef..0000000 --- a/aga-test/src/reducers/user.js +++ /dev/null @@ -1,36 +0,0 @@ -import { - USERS_UPDATE, - USER_UPDATE, - USER_CREATE, - USER_DELETE, -} from "../actions/user"; - -const initialState = { users: [] }; - -export const userReducer = (state = initialState, { type, payload }) => { - switch (type) { - case USERS_UPDATE: { - return { ...state, users: payload }; - } - case USER_UPDATE: { - const users = state.users.map((obj) => - payload.id === obj.id ? payload : obj - ); - return { ...state, users: users }; - } - case USER_DELETE: { - const users = state.users.filter(obj => obj.id !== payload.id); - - return { ...state, users: users }; - } - case USER_CREATE: { - const id = "_" + Math.random().toString(36).substr(2, 9); - const user = { ...payload, id: id }; - return { ...state, users: [...state.users, user] }; - } - default: - return state; - } -}; - -export default userReducer; From 49e8cade878457c901c25b5d47d6aca4ab6004fa Mon Sep 17 00:00:00 2001 From: agaskrobot Date: Thu, 9 Apr 2020 14:10:06 +0200 Subject: [PATCH 03/17] Finall changes. Added finall styles. --- aga-test-ts/src/App.test.tsx | 9 - aga-test-ts/src/api/contact.ts | 4 + aga-test-ts/src/components/AddContact.tsx | 9 +- aga-test-ts/src/components/Contact.tsx | 20 ++- aga-test-ts/src/containers/App.tsx | 60 ++++--- aga-test-ts/src/styles/_contact-add.scss | 35 ++++ aga-test-ts/src/styles/_contact-loading.scss | 17 ++ aga-test-ts/src/styles/_contact-row.scss | 43 +++++ aga-test-ts/src/styles/_container.scss | 21 +++ aga-test-ts/src/styles/style.css | 123 ++++++------- aga-test-ts/src/styles/style.css.map | 5 +- aga-test-ts/src/styles/style.scss | 173 +++---------------- 12 files changed, 268 insertions(+), 251 deletions(-) delete mode 100644 aga-test-ts/src/App.test.tsx create mode 100644 aga-test-ts/src/styles/_contact-add.scss create mode 100644 aga-test-ts/src/styles/_contact-loading.scss create mode 100644 aga-test-ts/src/styles/_contact-row.scss create mode 100644 aga-test-ts/src/styles/_container.scss diff --git a/aga-test-ts/src/App.test.tsx b/aga-test-ts/src/App.test.tsx deleted file mode 100644 index 09ba82c..0000000 --- a/aga-test-ts/src/App.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; -import { render } from "@testing-library/react"; -import { App } from "./containers/App"; - -test("renders learn react link", () => { - const { getByText } = render(); - const linkElement = getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/aga-test-ts/src/api/contact.ts b/aga-test-ts/src/api/contact.ts index 5c8a614..8980624 100644 --- a/aga-test-ts/src/api/contact.ts +++ b/aga-test-ts/src/api/contact.ts @@ -1,14 +1,18 @@ import axios from "axios/index"; import { Contact as ContactType } from "../store/contact/types"; +// Get all contacts export const getContactList = () => axios.get("https://jsonplaceholder.typicode.com/users"); +// Delete contact export const deleteContact = (contact: ContactType) => axios.delete(`https://jsonplaceholder.typicode.com/users/${contact.id}`); +// Update existing contact export const updateContact = (contact: ContactType) => axios.put(`https://jsonplaceholder.typicode.com/users/${contact.id}`,contact); +// Create new contact export const addContact = (contact: ContactType) => axios.post("https://jsonplaceholder.typicode.com/users", contact); diff --git a/aga-test-ts/src/components/AddContact.tsx b/aga-test-ts/src/components/AddContact.tsx index d8bccdc..2670eca 100644 --- a/aga-test-ts/src/components/AddContact.tsx +++ b/aga-test-ts/src/components/AddContact.tsx @@ -9,17 +9,18 @@ export const AddContact: FC = ({ onAdd }) => { const [name, setName] = useState(""); const [email, setEmail] = useState(""); + // Initial id is an epty string which is gonna be change when api is called const handleAdd = () => { const contact = { id: '', name: name, email: email }; onAdd(contact); }; return ( -
+
diff --git a/aga-test-ts/src/components/Contact.tsx b/aga-test-ts/src/components/Contact.tsx index 36ff6d2..25d9495 100644 --- a/aga-test-ts/src/components/Contact.tsx +++ b/aga-test-ts/src/components/Contact.tsx @@ -8,13 +8,12 @@ export interface Props { } export const Contact: FC = ({ contact, onDelete, onChange }) => { - const initialEmail = contact.email; - const [state, setState] = useState(false); - const [email, setEmail] = useState(initialEmail); + const [showEdit, setShowEdit] = useState(false); + const [email, setEmail] = useState(""); const handleChange = (e: React.MouseEvent) => { e.preventDefault(); - setState(true); + setShowEdit(true); }; const handleSave = ( @@ -22,7 +21,7 @@ export const Contact: FC = ({ contact, onDelete, onChange }) => { contact: ContactType ) => { e.preventDefault(); - setState(false); + setShowEdit(false); const updatedContact = { ...contact, email: email }; onChange(updatedContact); }; @@ -35,9 +34,12 @@ export const Contact: FC = ({ contact, onDelete, onChange }) => { onDelete(contact); }; - let input; - if (state === true) { - input = ( + // Edit form let change email. + // By default edit form is hide. + // It appers when "change email" button is clicked. + let editForm; + if (showEdit === true) { + editForm = ( = ({ contact, onDelete, onChange }) => { - {input} + {editForm} ); }; diff --git a/aga-test-ts/src/containers/App.tsx b/aga-test-ts/src/containers/App.tsx index ba7230c..edf99a2 100644 --- a/aga-test-ts/src/containers/App.tsx +++ b/aga-test-ts/src/containers/App.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, FC, Suspense } from "react"; +import React, { useState, useEffect, FC } from "react"; import { Dispatch } from "redux"; import { useSelector, useDispatch } from "react-redux"; @@ -24,19 +24,29 @@ import { RootState } from "../store/contact/index"; export const App: FC = () => { const dispatch = useDispatch>(); const contacts = useSelector((state: RootState) => state.contactReducer); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(false); + // useEffect load all contacts. useEffect(() => { setLoading(true); getContactList() .then((u) => dispatch(loadAllContacts(u.data))) + .catch(() => setError(true)) .finally(() => setLoading(false)); }, []); + // If there is an error display simple message + if (error === true) { + return

Something went wrong...

; + } + const handleChange = (contact: ContactType) => { setLoading(true); updateContact(contact) .then(() => dispatch(contactUpdate(contact))) + .catch(() => setError(true)) .finally(() => setLoading(false)); }; @@ -44,6 +54,7 @@ export const App: FC = () => { setLoading(true); deleteContact(contact) .then(() => dispatch(contactDelete(contact))) + .catch(() => setError(true)) .finally(() => setLoading(false)); }; @@ -51,33 +62,36 @@ export const App: FC = () => { setLoading(true); addContact(contact) .then((u) => dispatch(contactCreate(u.data))) + .catch(() => setError(true)) .finally(() => setLoading(false)); }; return ( -
+
diff --git a/aga-test-ts/src/styles/_contact-add.scss b/aga-test-ts/src/styles/_contact-add.scss new file mode 100644 index 0000000..86cab5e --- /dev/null +++ b/aga-test-ts/src/styles/_contact-add.scss @@ -0,0 +1,35 @@ +.contact-add { + width: 100%; + position: relative; + &__btn { + @include btn(); + width: 70px; + background-color: #36b03c; + font-size: 20px; + padding: 15px 10px; + margin-top: 20px; + &:focus { + -webkit-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + -moz-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + } + &:hover { + background-color: #37b93d; + } + } + &__input { + @include input(); + width: 20%; + font-size: 20px; + padding: 10px; + margin: 10px; + &:focus { + -webkit-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + -moz-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + border: 2px solid #a5cda5; + background-color: #e9f3e9; + color: #428c42; + } + } +} \ No newline at end of file diff --git a/aga-test-ts/src/styles/_contact-loading.scss b/aga-test-ts/src/styles/_contact-loading.scss new file mode 100644 index 0000000..2d684c7 --- /dev/null +++ b/aga-test-ts/src/styles/_contact-loading.scss @@ -0,0 +1,17 @@ +.contact-loading { + margin: 0; + border: 0; + padding: 0; + display: inline-block; + vertical-align: middle; + white-space: normal; + background: none; + outline: none; + &__title { + margin-top: 20px; + } + &__spinner { + height: 110px; + margin: 20px; + } +} \ No newline at end of file diff --git a/aga-test-ts/src/styles/_contact-row.scss b/aga-test-ts/src/styles/_contact-row.scss new file mode 100644 index 0000000..b4511d4 --- /dev/null +++ b/aga-test-ts/src/styles/_contact-row.scss @@ -0,0 +1,43 @@ +.contact-row { + padding-top: 7px; + &__input { + @include input(); + width: auto; + font-size: 15px; + padding: 3px; + &:focus { + -webkit-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + -moz-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + border: 2px solid #eec596; + background-color: #f0d6b8; + color: #8c7242; + } + } + &__input{ + max-width: 80%; + } + &__btn { + @include btn(); + margin-left: auto; + width: auto; + background-color: #e0841a; + font-size: 15px; + padding: 5px 5px; + &.delete { + background-color: #d63619; + &:hover { + background-color: #ec634a; + } + } + + &:focus { + -webkit-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + -moz-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + } + &:hover { + background-color: #faa13b; + } + } +} diff --git a/aga-test-ts/src/styles/_container.scss b/aga-test-ts/src/styles/_container.scss new file mode 100644 index 0000000..7660679 --- /dev/null +++ b/aga-test-ts/src/styles/_container.scss @@ -0,0 +1,21 @@ +.container { + padding-top: 20px; + background: #fafafa; + display: flex; + margin: 0; + min-width: 500px; + max-width: 80%; + text-align: center; + align-items: center; + flex-direction: column; + justify-content: center; + border-radius: 5px; + box-shadow: 0 0 5px rgba(25, 25, 25, 0.25); + padding: 3rem; +} + +.container-title { + margin-bottom: auto; + margin-top: 0; + line-height: 1; +} diff --git a/aga-test-ts/src/styles/style.css b/aga-test-ts/src/styles/style.css index 2f2d4a3..97e9d88 100644 --- a/aga-test-ts/src/styles/style.css +++ b/aga-test-ts/src/styles/style.css @@ -1,4 +1,36 @@ @import url("https://fonts.googleapis.com/css2?family=Varela+Round&display=swap"); +.container { + padding-top: 20px; + background: #fafafa; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + margin: 0; + min-width: 500px; + max-width: 80%; + text-align: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + border-radius: 5px; + -webkit-box-shadow: 0 0 5px rgba(25, 25, 25, 0.25); + box-shadow: 0 0 5px rgba(25, 25, 25, 0.25); + padding: 3rem; +} + +.container-title { + margin-bottom: auto; + margin-top: 0; + line-height: 1; +} + html, body { width: 100%; @@ -10,55 +42,24 @@ body { color: #2e2e2e; font-size: 18px; line-height: 1.5; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; background: #b3b3ff; background: linear-gradient(45deg, #b3b3ff, #8080ff); - height: 100%; margin: 0; padding: 0; } -.contacts-app { - padding-top: 20px; - min-width: 800px; - max-width: 80%; - min-height: 480px; - max-height: 60%; - background: #fafafa; +#root { + padding-top: 15px; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; display: -webkit-box; display: -ms-flexbox; display: flex; - margin: 0; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - -ms-flex-direction: column; - flex-direction: column; -webkit-box-align: center; -ms-flex-align: center; align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - border-radius: 5px; - -webkit-box-shadow: 0 0 5px rgba(25, 25, 25, 0.25); - box-shadow: 0 0 5px rgba(25, 25, 25, 0.25); - padding: 3rem; - position: absolute; - top: 50%; - left: 50%; - -webkit-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); -} - -.contacts-title { - margin-bottom: auto; - margin-top: 0; - line-height: 1; + min-width: 600px; } .contact-loading { @@ -81,50 +82,50 @@ body { margin: 20px; } -.add-contact { +.contact-add { width: 100%; position: relative; } -.add-contact__btn { +.contact-add__btn { font-family: "Varela Round", sans-serif; - width: 70px; - background-color: #36b03c; - font-size: 20px; color: white; - padding: 15px 10px; - margin-top: 20px; border: none; border-radius: 5px; cursor: pointer; letter-spacing: 2px; outline: none; + width: 70px; + background-color: #36b03c; + font-size: 20px; + padding: 15px 10px; + margin-top: 20px; } -.add-contact__btn:focus { +.contact-add__btn:focus { -webkit-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); } -.add-contact__btn:hover { +.contact-add__btn:hover { background-color: #37b93d; } -.add-contact__input { - width: 20%; +.contact-add__input { background-color: #efefef; color: #666; border: 2px solid #ddd; border-radius: 5px; - font-size: 20px; - padding: 10px; -webkit-box-sizing: border-box; box-sizing: border-box; outline: none; + width: 20%; + font-size: 20px; + padding: 10px; margin: 10px; } -.add-contact__input:focus { +.contact-add__input:focus { -webkit-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); border: 2px solid #a5cda5; @@ -139,14 +140,14 @@ body { .contact-row__input { background-color: #efefef; color: #666; - width: 200px; border: 2px solid #ddd; border-radius: 5px; - font-size: 15px; - padding: 3px; -webkit-box-sizing: border-box; box-sizing: border-box; outline: none; + width: auto; + font-size: 15px; + padding: 3px; } .contact-row__input:focus { @@ -157,19 +158,23 @@ body { color: #8c7242; } +.contact-row__input { + max-width: 80%; +} + .contact-row__btn { - margin-left: auto; font-family: "Varela Round", sans-serif; - width: auto; - background-color: #e0841a; - font-size: 15px; color: white; - padding: 5px 5px; border: none; border-radius: 5px; cursor: pointer; letter-spacing: 2px; outline: none; + margin-left: auto; + width: auto; + background-color: #e0841a; + font-size: 15px; + padding: 5px 5px; } .contact-row__btn.delete { diff --git a/aga-test-ts/src/styles/style.css.map b/aga-test-ts/src/styles/style.css.map index 0b1be09..069c7ac 100644 --- a/aga-test-ts/src/styles/style.css.map +++ b/aga-test-ts/src/styles/style.css.map @@ -1,8 +1,9 @@ { "version": 3, - "mappings": "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", + "mappings": "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", "sources": [ - "style.scss" + "style.scss", + "_container.scss" ], "names": [], "file": "style.css" diff --git a/aga-test-ts/src/styles/style.scss b/aga-test-ts/src/styles/style.scss index 6b8bcd8..afe41c5 100644 --- a/aga-test-ts/src/styles/style.scss +++ b/aga-test-ts/src/styles/style.scss @@ -1,4 +1,27 @@ @import url("https://fonts.googleapis.com/css2?family=Varela+Round&display=swap"); +@import './container'; +@import './contact-add'; +@import './contact-loading'; +@import './contact-row'; + +@mixin btn { + font-family: "Varela Round", sans-serif; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + letter-spacing: 2px; + outline: none; +} + +@mixin input { + background-color: #efefef; + color: #666; + border: 2px solid #ddd; + border-radius: 5px; + box-sizing: border-box; + outline: none; +} html, body { @@ -11,155 +34,15 @@ body { color: #2e2e2e; font-size: 18px; line-height: 1.5; - align-items: center; - justify-content: center; background: #b3b3ff; background: linear-gradient(45deg, #b3b3ff, #8080ff); - height: 100%; margin: 0; padding: 0; } - -.contacts-app { - padding-top: 20px; - min-width: 800px; - max-width: 80%; - min-height: 480px; - max-height: 60%; - background: #fafafa; +#root { + padding-top: 15px; + justify-content: center; display: flex; - margin: 0; - flex-direction: column; align-items: center; - justify-content: center; - border-radius: 5px; - box-shadow: 0 0 5px rgba(25, 25, 25, 0.25); - padding: 3rem; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.contacts-title { - margin-bottom: auto; - margin-top: 0; - line-height: 1; -} - -.contact-loading { - margin: 0; - border: 0; - padding: 0; - display: inline-block; - vertical-align: middle; - white-space: normal; - background: none; - outline: none; - &__title { - margin-top: 20px; - } - &__spinner { - height: 110px; - margin: 20px; - } -} - -.add-contact { - width: 100%; - position: relative; - &__btn { - font-family: "Varela Round", sans-serif; - width: 70px; - background-color: #36b03c; - font-size: 20px; - color: white; - padding: 15px 10px; - margin-top: 20px; - border: none; - border-radius: 5px; - cursor: pointer; - letter-spacing: 2px; - outline: none; - &:focus { - -webkit-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); - -moz-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); - box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); - } - &:hover { - background-color: #37b93d; - } - } - &__input { - width: 20%; - background-color: #efefef; - color: #666; - border: 2px solid #ddd; - border-radius: 5px; - font-size: 20px; - padding: 10px; - box-sizing: border-box; - outline: none; - margin: 10px; - &:focus { - -webkit-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); - -moz-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); - box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); - border: 2px solid #a5cda5; - background-color: #e9f3e9; - color: #428c42; - } - } -} - -.contact-row { - padding-top: 7px;; - &__input { - background-color: #efefef; - color: #666; - width: 200px; - border: 2px solid #ddd; - border-radius: 5px; - font-size: 15px; - padding: 3px; - box-sizing: border-box; - outline: none; - &:focus { - -webkit-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); - -moz-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); - box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); - border: 2px solid #eec596; - background-color: #f0d6b8; - color: #8c7242; - } - } - &__btn { - margin-left: auto; - font-family: "Varela Round", sans-serif; - width: auto; - background-color: #e0841a; - font-size: 15px; - color: white; - padding: 5px 5px; - border: none; - border-radius: 5px; - cursor: pointer; - letter-spacing: 2px; - outline: none; - &.delete{ - background-color: #d63619; - &:hover { - background-color: #ec634a; - } - } - - &:focus { - -webkit-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); - -moz-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); - box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); - } - &:hover { - background-color: #faa13b; - } - } -} + min-width: 600px; +} \ No newline at end of file From 261309547f2dc92fb054ee4259c5d0ecdcd17e35 Mon Sep 17 00:00:00 2001 From: agaskrobot Date: Thu, 9 Apr 2020 14:58:18 +0200 Subject: [PATCH 04/17] fixed little issue in styles! --- aga-test-ts/src/styles/_contact-add.scss | 4 +- aga-test-ts/src/styles/_contact-row.scss | 2 + aga-test-ts/src/styles/_mixin.scss | 18 ++++ aga-test-ts/src/styles/style.css | 101 +++++++++++------------ aga-test-ts/src/styles/style.css.map | 10 ++- aga-test-ts/src/styles/style.scss | 33 ++------ 6 files changed, 88 insertions(+), 80 deletions(-) create mode 100644 aga-test-ts/src/styles/_mixin.scss diff --git a/aga-test-ts/src/styles/_contact-add.scss b/aga-test-ts/src/styles/_contact-add.scss index 86cab5e..e1d5fa4 100644 --- a/aga-test-ts/src/styles/_contact-add.scss +++ b/aga-test-ts/src/styles/_contact-add.scss @@ -1,3 +1,5 @@ +@import "./mixin"; + .contact-add { width: 100%; position: relative; @@ -32,4 +34,4 @@ color: #428c42; } } -} \ No newline at end of file +} diff --git a/aga-test-ts/src/styles/_contact-row.scss b/aga-test-ts/src/styles/_contact-row.scss index b4511d4..221c6bd 100644 --- a/aga-test-ts/src/styles/_contact-row.scss +++ b/aga-test-ts/src/styles/_contact-row.scss @@ -1,3 +1,5 @@ +@import "./mixin"; + .contact-row { padding-top: 7px; &__input { diff --git a/aga-test-ts/src/styles/_mixin.scss b/aga-test-ts/src/styles/_mixin.scss new file mode 100644 index 0000000..5246b6c --- /dev/null +++ b/aga-test-ts/src/styles/_mixin.scss @@ -0,0 +1,18 @@ +@mixin btn { + font-family: "Varela Round", sans-serif; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + letter-spacing: 2px; + outline: none; +} + +@mixin input { + background-color: #efefef; + color: #666; + border: 2px solid #ddd; + border-radius: 5px; + box-sizing: border-box; + outline: none; +} \ No newline at end of file diff --git a/aga-test-ts/src/styles/style.css b/aga-test-ts/src/styles/style.css index 97e9d88..54663d1 100644 --- a/aga-test-ts/src/styles/style.css +++ b/aga-test-ts/src/styles/style.css @@ -31,57 +31,6 @@ line-height: 1; } -html, -body { - width: 100%; - height: 100%; -} - -body { - font-family: "Varela Round", sans-serif; - color: #2e2e2e; - font-size: 18px; - line-height: 1.5; - background: #b3b3ff; - background: linear-gradient(45deg, #b3b3ff, #8080ff); - margin: 0; - padding: 0; -} - -#root { - padding-top: 15px; - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - min-width: 600px; -} - -.contact-loading { - margin: 0; - border: 0; - padding: 0; - display: inline-block; - vertical-align: middle; - white-space: normal; - background: none; - outline: none; -} - -.contact-loading__title { - margin-top: 20px; -} - -.contact-loading__spinner { - height: 110px; - margin: 20px; -} - .contact-add { width: 100%; position: relative; @@ -133,6 +82,26 @@ body { color: #428c42; } +.contact-loading { + margin: 0; + border: 0; + padding: 0; + display: inline-block; + vertical-align: middle; + white-space: normal; + background: none; + outline: none; +} + +.contact-loading__title { + margin-top: 20px; +} + +.contact-loading__spinner { + height: 110px; + margin: 20px; +} + .contact-row { padding-top: 7px; } @@ -193,4 +162,34 @@ body { .contact-row__btn:hover { background-color: #faa13b; } + +html, +body { + width: 100%; + height: auto; +} + +body { + font-family: "Varela Round", sans-serif; + color: #2e2e2e; + font-size: 18px; + line-height: 1.5; + background: #b3b3ff; + margin: 0; + padding: 0; +} + +#root { + padding-top: 15px; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + min-width: 600px; +} /*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/aga-test-ts/src/styles/style.css.map b/aga-test-ts/src/styles/style.css.map index 069c7ac..51032f4 100644 --- a/aga-test-ts/src/styles/style.css.map +++ b/aga-test-ts/src/styles/style.css.map @@ -1,9 +1,15 @@ { "version": 3, - "mappings": "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", + "mappings": "AAAA,OAAO,CAAC,yEAAI;ACAZ,AAAA,UAAU,CAAC;EACT,WAAW,EAAE,IAAI;EACjB,UAAU,EAAE,OAAO;EACnB,OAAO,EAAE,IAAI;EACb,MAAM,EAAE,CAAC;EACT,SAAS,EAAE,KAAK;EAChB,SAAS,EAAE,GAAG;EACd,UAAU,EAAE,MAAM;EAClB,WAAW,EAAE,MAAM;EACnB,cAAc,EAAE,MAAM;EACtB,eAAe,EAAE,MAAM;EACvB,aAAa,EAAE,GAAG;EAClB,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,sBAAsB;EAC1C,OAAO,EAAE,IAAI;CACd;;AAED,AAAA,gBAAgB,CAAC;EACf,aAAa,EAAE,IAAI;EACnB,UAAU,EAAE,CAAC;EACb,WAAW,EAAE,CAAC;CACf;;AClBD,AAAA,YAAY,CAAC;EACX,KAAK,EAAE,IAAI;EACX,QAAQ,EAAE,QAAQ;CAgCnB;;AA/BE,AAAD,iBAAM,CAAC;ECJP,WAAW,EAAE,0BAA0B;EACvC,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,IAAI;EACZ,aAAa,EAAE,GAAG;EAClB,MAAM,EAAE,OAAO;EACf,cAAc,EAAE,GAAG;EACnB,OAAO,EAAE,IAAI;EDAX,KAAK,EAAE,IAAI;EACX,gBAAgB,EAAE,OAAO;EACzB,SAAS,EAAE,IAAI;EACf,OAAO,EAAE,SAAS;EAClB,UAAU,EAAE,IAAI;CASjB;;AAfA,AAOC,iBAPI,AAOH,MAAM,CAAC;EACN,kBAAkB,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB;EAC7D,eAAe,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB;EAC1D,UAAU,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB;CACtD;;AAXF,AAYC,iBAZI,AAYH,MAAM,CAAC;EACN,gBAAgB,EAAE,OAAO;CAC1B;;AAEF,AAAD,mBAAQ,CAAC;ECVT,gBAAgB,EAAE,OAAO;EACzB,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,cAAc;EACtB,aAAa,EAAE,GAAG;EAClB,UAAU,EAAE,UAAU;EACtB,OAAO,EAAE,IAAI;EDOX,KAAK,EAAE,GAAG;EACV,SAAS,EAAE,IAAI;EACf,OAAO,EAAE,IAAI;EACb,MAAM,EAAE,IAAI;CASb;;AAdA,AAMC,mBANM,AAML,MAAM,CAAC;EACN,kBAAkB,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB;EAC7D,eAAe,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB;EAC1D,UAAU,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB;EACrD,MAAM,EAAE,iBAAiB;EACzB,gBAAgB,EAAE,OAAO;EACzB,KAAK,EAAE,OAAO;CACf;;AElCL,AAAA,gBAAgB,CAAC;EACf,MAAM,EAAE,CAAC;EACT,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,CAAC;EACV,OAAO,EAAE,YAAY;EACrB,cAAc,EAAE,MAAM;EACtB,WAAW,EAAE,MAAM;EACnB,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,IAAI;CAQd;;AAPE,AAAD,uBAAQ,CAAC;EACP,UAAU,EAAE,IAAI;CACjB;;AACA,AAAD,yBAAU,CAAC;EACT,MAAM,EAAE,KAAK;EACb,MAAM,EAAE,IAAI;CACb;;ACbH,AAAA,YAAY,CAAC;EACX,WAAW,EAAE,GAAG;CAyCjB;;AAxCE,AAAD,mBAAQ,CAAC;EFOT,gBAAgB,EAAE,OAAO;EACzB,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,cAAc;EACtB,aAAa,EAAE,GAAG;EAClB,UAAU,EAAE,UAAU;EACtB,OAAO,EAAE,IAAI;EEVX,KAAK,EAAE,IAAI;EACX,SAAS,EAAE,IAAI;EACf,OAAO,EAAE,GAAG;CASb;;AAbA,AAKC,mBALM,AAKL,MAAM,CAAC;EACN,kBAAkB,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB;EAC7D,eAAe,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB;EAC1D,UAAU,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB;EACrD,MAAM,EAAE,iBAAiB;EACzB,gBAAgB,EAAE,OAAO;EACzB,KAAK,EAAE,OAAO;CACf;;AAEF,AAAD,mBAAQ,CAAA;EACN,SAAS,EAAE,GAAG;CACf;;AACA,AAAD,iBAAM,CAAC;EFpBP,WAAW,EAAE,0BAA0B;EACvC,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,IAAI;EACZ,aAAa,EAAE,GAAG;EAClB,MAAM,EAAE,OAAO;EACf,cAAc,EAAE,GAAG;EACnB,OAAO,EAAE,IAAI;EEgBX,WAAW,EAAE,IAAI;EACjB,KAAK,EAAE,IAAI;EACX,gBAAgB,EAAE,OAAO;EACzB,SAAS,EAAE,IAAI;EACf,OAAO,EAAE,OAAO;CAgBjB;;AAtBA,AAOC,iBAPI,AAOH,OAAO,CAAC;EACP,gBAAgB,EAAE,OAAO;CAI1B;;AAZF,AASG,iBATE,AAOH,OAAO,AAEL,MAAM,CAAC;EACN,gBAAgB,EAAE,OAAO;CAC1B;;AAXJ,AAcC,iBAdI,AAcH,MAAM,CAAC;EACN,kBAAkB,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB;EAC7D,eAAe,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB;EAC1D,UAAU,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB;CACtD;;AAlBF,AAmBC,iBAnBI,AAmBH,MAAM,CAAC;EACN,gBAAgB,EAAE,OAAO;CAC1B;;ALnCL,AAAA,IAAI;AACJ,IAAI,CAAC;EACH,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;CACb;;AAED,AAAA,IAAI,CAAC;EACH,WAAW,EAAE,0BAA0B;EACvC,KAAK,EAAE,OAAO;EACd,SAAS,EAAE,IAAI;EACf,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,OAAO;EACnB,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,CAAC;CACX;;AACD,AAAA,KAAK,CAAC;EACJ,WAAW,EAAE,IAAI;EACjB,eAAe,EAAE,MAAM;EACvB,OAAO,EAAE,IAAI;EACb,WAAW,EAAE,MAAM;EACnB,SAAS,EAAE,KAAK;CACjB", "sources": [ "style.scss", - "_container.scss" + "_container.scss", + "_contact-add.scss", + "_mixin.scss", + "_contact-loading.scss", + "_contact-row.scss", + "_mixin.scss", + "_mixin.scss" ], "names": [], "file": "style.css" diff --git a/aga-test-ts/src/styles/style.scss b/aga-test-ts/src/styles/style.scss index afe41c5..c868296 100644 --- a/aga-test-ts/src/styles/style.scss +++ b/aga-test-ts/src/styles/style.scss @@ -1,32 +1,14 @@ @import url("https://fonts.googleapis.com/css2?family=Varela+Round&display=swap"); -@import './container'; -@import './contact-add'; -@import './contact-loading'; -@import './contact-row'; - -@mixin btn { - font-family: "Varela Round", sans-serif; - color: white; - border: none; - border-radius: 5px; - cursor: pointer; - letter-spacing: 2px; - outline: none; -} - -@mixin input { - background-color: #efefef; - color: #666; - border: 2px solid #ddd; - border-radius: 5px; - box-sizing: border-box; - outline: none; -} +@import "./container"; +@import "./contact-add"; +@import "./contact-loading"; +@import "./contact-row"; +@import "./mixin"; html, body { width: 100%; - height: 100%; + height: auto; } body { @@ -35,7 +17,6 @@ body { font-size: 18px; line-height: 1.5; background: #b3b3ff; - background: linear-gradient(45deg, #b3b3ff, #8080ff); margin: 0; padding: 0; } @@ -45,4 +26,4 @@ body { display: flex; align-items: center; min-width: 600px; -} \ No newline at end of file +} From 719a24506b1fbb3200690c85607c947eb5f6b53a Mon Sep 17 00:00:00 2001 From: agaskrobot Date: Thu, 9 Apr 2020 18:18:30 +0200 Subject: [PATCH 05/17] Moving test to 01 Testing components. --- 01 Testing components/README.md | 20 +- 01 Testing components/config/webpack/base.js | 17 +- 01 Testing components/package.json | 47 +++-- 01 Testing components/src/app.tsx | 4 +- 01 Testing components/src/index.tsx | 24 ++- 01 Testing components/src/myApi/index.ts | 8 +- .../src/myApi/myContactApi.ts | 18 ++ .../src/myApi/{myApi.ts => myFruitApi.ts} | 0 .../src/myComponent/index.ts | 5 +- .../src/myComponent/myAddContact.tsx | 43 ++++ .../src/myComponent/myComponent.spec.tsx | 42 ++-- .../src/myComponent/myComponent.tsx | 114 ++++++++-- .../src/myComponent/myContact.tsx | 85 ++++++++ .../src/myComponent/myFruits.tsx | 16 +- .../src/myComponent/myLoading.tsx | 23 +++ .../src/store/contact/actions.ts | 24 +++ .../src/store/contact/index.ts | 8 + .../src/store/contact/reducers.ts | 32 +++ .../src/store/contact/types.ts | 42 ++++ .../src/styles/_contact-add.scss | 37 ++++ .../src/styles/_contact-loading.scss | 17 ++ .../src/styles/_contact-row.scss | 45 ++++ .../src/styles/_container.scss | 21 ++ 01 Testing components/src/styles/_mixin.scss | 18 ++ 01 Testing components/src/styles/style.css | 195 ++++++++++++++++++ .../src/styles/style.css.map | 16 ++ 01 Testing components/src/styles/style.scss | 29 +++ 27 files changed, 870 insertions(+), 80 deletions(-) create mode 100644 01 Testing components/src/myApi/myContactApi.ts rename 01 Testing components/src/myApi/{myApi.ts => myFruitApi.ts} (100%) create mode 100644 01 Testing components/src/myComponent/myAddContact.tsx create mode 100644 01 Testing components/src/myComponent/myContact.tsx create mode 100644 01 Testing components/src/myComponent/myLoading.tsx create mode 100644 01 Testing components/src/store/contact/actions.ts create mode 100644 01 Testing components/src/store/contact/index.ts create mode 100644 01 Testing components/src/store/contact/reducers.ts create mode 100644 01 Testing components/src/store/contact/types.ts create mode 100644 01 Testing components/src/styles/_contact-add.scss create mode 100644 01 Testing components/src/styles/_contact-loading.scss create mode 100644 01 Testing components/src/styles/_contact-row.scss create mode 100644 01 Testing components/src/styles/_container.scss create mode 100644 01 Testing components/src/styles/_mixin.scss create mode 100644 01 Testing components/src/styles/style.css create mode 100644 01 Testing components/src/styles/style.css.map create mode 100644 01 Testing components/src/styles/style.scss diff --git a/01 Testing components/README.md b/01 Testing components/README.md index 84d3e4a..937c914 100644 --- a/01 Testing components/README.md +++ b/01 Testing components/README.md @@ -205,11 +205,11 @@ Since we want to trigger the `change` event, we simply need to call the `fireEve ## Running asynchronous calls and unit tests -Now let us modify our component so that it performs a call against an API to retrieve some data after initialization. This is a side effect and can be implemented using a `useEffect` hook. We will also import the `getListOfFruit` method from `myApi` folder, which simulates an async call to retrieve a list of pieces of fruit. +Now let us modify our component so that it performs a call against an API to retrieve some data after initialization. This is a side effect and can be implemented using a `useEffect` hook. We will also import the `getListOfFruit` method from `myFruitApi` folder, which simulates an async call to retrieve a list of pieces of fruit. ```diff import * as React from 'react'; -+import { getListOfFruit } from '../myApi'; ++import { getListOfFruit } from '../myFruitApi'; export interface Props { nameFromProps: string; @@ -250,13 +250,13 @@ So now our component will always perform a (fake) fetch call on initialization, import * as React from 'react'; -import { render, fireEvent } from '@testing-library/react'; +import { render, fireEvent, waitForElement } from '@testing-library/react'; -+import * as myApi from '../myApi'; ++import * as myFruitApi from '../myFruitApi'; import { MyComponent, Props } from './myComponent'; ... + it('should display the list of fruits after resolving the api call on initialization', async () => { + // Arrange + const getListOfFruitMock = jest -+ .spyOn(myApi, 'getListOfFruit') ++ .spyOn(myFruitApi, 'getListOfFruit') + .mockResolvedValue(['Melon', 'Apple', 'Pear']); + + // Act @@ -282,13 +282,13 @@ However, if we run the code (and we are using React 16.8.6 or lower), we will se import * as React from 'react'; -import { render, fireEvent, waitForElement } from '@testing-library/react'; +import { render, fireEvent, waitForElement, act, RenderResult } from '@testing-library/react'; -import * as myApi from '../myApi'; +import * as myFruitApi from '../myFruitApi'; import { MyComponent, Props } from './myComponent'; ... it('should display the list of fruits after resolving the api call on initialization', async () => { // Arrange const getListOfFruitMock = jest - .spyOn(myApi, 'getListOfFruit') + .spyOn(myFruitApi, 'getListOfFruit') .mockResolvedValue(['Melon', 'Apple', 'Pear']); // Act @@ -315,7 +315,7 @@ This fixes the issue with this test. Technically, the other tests also share the ```diff import * as React from 'react'; --import { getListOfFruit } from '../myApi'; +-import { getListOfFruit } from '../myFruitApi'; +import { MyFruits } from './myFruits'; export interface Props { @@ -355,7 +355,7 @@ export const MyComponent: React.FunctionComponent = props => { And the code for our new `MyFruits` component can be found below ```javascript import * as React from 'react'; -import { getListOfFruit } from '../myApi'; +import { getListOfFruit } from '../myFruitApi'; export const MyFruits = (props) => { const [fruits, setFruits] = React.useState([]); @@ -384,7 +384,7 @@ The first thing we are going to do is disable the last test we coded as it does - it('should display the list of fruits after resolving the api call on initialization', async () => { // Arrange const getListOfFruitMock = jest - .spyOn(myApi, 'getListOfFruit') + .spyOn(myFruitApi, 'getListOfFruit') .mockResolvedValue(['Melon', 'Apple', 'Pear']); // Act @@ -395,7 +395,7 @@ And now, in order to mock our component, let us just mock the whole module that ```diff import * as React from 'react'; import { render, fireEvent, waitForElement, act, RenderResult } from '@testing-library/react'; -import * as myApi from '../myApi'; +import * as myFruitApi from '../myFruitApi'; import { MyComponent, Props } from './myComponent'; +jest.mock('./myFruits.tsx', () => ({ diff --git a/01 Testing components/config/webpack/base.js b/01 Testing components/config/webpack/base.js index 116fcaf..ae59f05 100644 --- a/01 Testing components/config/webpack/base.js +++ b/01 Testing components/config/webpack/base.js @@ -8,7 +8,7 @@ module.exports = merge( { context: helpers.resolveFromRootPath('src'), resolve: { - extensions: ['.js', '.ts', '.tsx'], + extensions: ['.js', '.ts', '.tsx', '.css', '.scss'], }, entry: { app: ['./index.tsx'], @@ -25,6 +25,21 @@ module.exports = merge( babelCore: '@babel/core', }, }, + { + test: /\.css$/i, + use: ['style-loader', 'css-loader'], + }, + { + test: /\.s[ac]ss$/i, + use: [ + // Creates `style` nodes from JS strings + 'style-loader', + // Translates CSS into CommonJS + 'css-loader', + // Compiles Sass to CSS + 'sass-loader', + ], + }, ], }, optimization: { diff --git a/01 Testing components/package.json b/01 Testing components/package.json index c70f345..e94b3f0 100644 --- a/01 Testing components/package.json +++ b/01 Testing components/package.json @@ -14,29 +14,44 @@ "license": "MIT", "dependencies": { "@material-ui/core": "^4.1.3", - "axios": "^0.19.0", - "react": "^16.8.6", - "react-dom": "^16.8.6", - "react-router-dom": "^5.0.1" + "@types/react-redux": "^7.1.7", + "@types/redux": "^3.6.0", + "axios": "^0.19.2", + "install": "^0.13.0", + "npm": "^6.14.4", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-redux": "^7.2.0", + "react-router-dom": "^5.0.1", + "react-spinners": "^0.8.1", + "redux": "^4.0.5", + "style-loader": "^1.1.3" }, "devDependencies": { "@babel/cli": "^7.4.4", - "@babel/core": "^7.4.5", - "@babel/preset-env": "^7.4.5", - "@testing-library/react": "^8.0.7", - "@types/jest": "^24.0.13", - "@types/react": "^16.8.19", - "@types/react-dom": "^16.8.4", + "@babel/core": "^7.9.0", + "@babel/preset-env": "^7.9.5", + "@babel/preset-react": "^7.9.4", + "@testing-library/react": "^8.0.9", + "@types/jest": "^24.9.1", + "@types/react": "^16.9.33", + "@types/react-dom": "^16.9.6", "@types/react-router-dom": "^4.3.4", "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.1.0", + "css-loader": "^3.5.1", "html-webpack-plugin": "^3.2.0", "jest": "^24.8.0", + "node-sass": "^4.13.1", "rimraf": "^2.6.3", - "ts-jest": "^24.0.2", - "typescript": "^3.5.2", - "webpack": "^4.32.2", - "webpack-cli": "^3.3.2", - "webpack-dev-server": "^3.5.0", - "webpack-merge": "^4.2.1" + "sass-loader": "^8.0.2", + "source-map-loader": "^0.2.4", + "ts-jest": "^24.3.0", + "ts-loader": "^6.2.2", + "typescript": "^3.8.3", + "webpack": "^4.42.1", + "webpack-cli": "^3.3.11", + "webpack-dev-server": "^3.10.3", + "webpack-merge": "^4.2.2" } } diff --git a/01 Testing components/src/app.tsx b/01 Testing components/src/app.tsx index f201da0..9276af1 100644 --- a/01 Testing components/src/app.tsx +++ b/01 Testing components/src/app.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { MyComponent } from './myComponent'; -export const App: React.FunctionComponent = props => ( +export const App: React.FunctionComponent = () => (
- +
); diff --git a/01 Testing components/src/index.tsx b/01 Testing components/src/index.tsx index ded7936..9ca89b3 100644 --- a/01 Testing components/src/index.tsx +++ b/01 Testing components/src/index.tsx @@ -1,5 +1,21 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { App } from './app'; +import React from "react"; +import ReactDOM from "react-dom"; +import "./styles/style.css"; +import { App } from "./app"; + +import { createStore } from "redux"; +import { Provider } from "react-redux"; + +import { rootReducer } from "./store/contact/index"; + +const store = createStore(rootReducer) + +ReactDOM.render( + + + + + , + document.getElementById("root") +); -ReactDOM.render(, document.getElementById('root')); diff --git a/01 Testing components/src/myApi/index.ts b/01 Testing components/src/myApi/index.ts index 3e74d70..24ab56a 100644 --- a/01 Testing components/src/myApi/index.ts +++ b/01 Testing components/src/myApi/index.ts @@ -1 +1,7 @@ -export { getListOfFruit } from './myApi'; \ No newline at end of file +export { getListOfFruit } from './myFruitApi'; +export { + getContactList, + updateContact, + deleteContact, + addContact, +} from './myContactApi'; diff --git a/01 Testing components/src/myApi/myContactApi.ts b/01 Testing components/src/myApi/myContactApi.ts new file mode 100644 index 0000000..8980624 --- /dev/null +++ b/01 Testing components/src/myApi/myContactApi.ts @@ -0,0 +1,18 @@ +import axios from "axios/index"; +import { Contact as ContactType } from "../store/contact/types"; + +// Get all contacts +export const getContactList = () => + axios.get("https://jsonplaceholder.typicode.com/users"); + +// Delete contact +export const deleteContact = (contact: ContactType) => + axios.delete(`https://jsonplaceholder.typicode.com/users/${contact.id}`); + +// Update existing contact +export const updateContact = (contact: ContactType) => + axios.put(`https://jsonplaceholder.typicode.com/users/${contact.id}`,contact); + +// Create new contact +export const addContact = (contact: ContactType) => + axios.post("https://jsonplaceholder.typicode.com/users", contact); diff --git a/01 Testing components/src/myApi/myApi.ts b/01 Testing components/src/myApi/myFruitApi.ts similarity index 100% rename from 01 Testing components/src/myApi/myApi.ts rename to 01 Testing components/src/myApi/myFruitApi.ts diff --git a/01 Testing components/src/myComponent/index.ts b/01 Testing components/src/myComponent/index.ts index bcc7da0..216c29d 100644 --- a/01 Testing components/src/myComponent/index.ts +++ b/01 Testing components/src/myComponent/index.ts @@ -1 +1,4 @@ -export { MyComponent } from './myComponent'; \ No newline at end of file +export { MyComponent } from './myComponent'; +export { MyAddContact } from './myAddContact'; +export { MyContact } from './myContact'; +export { MyLoading } from './myLoading'; \ No newline at end of file diff --git a/01 Testing components/src/myComponent/myAddContact.tsx b/01 Testing components/src/myComponent/myAddContact.tsx new file mode 100644 index 0000000..182c114 --- /dev/null +++ b/01 Testing components/src/myComponent/myAddContact.tsx @@ -0,0 +1,43 @@ +import React, { useState, FC } from "react"; +import { Contact as ContactType } from "../store/contact/types"; + +export interface Props { + onAdd(contact: ContactType): void; +} + +export const MyAddContact: FC = ({ onAdd }) => { + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + + // Initial id is an epty string which is gonna be change when api is called + const handleAdd = () => { + const contact = { id: '', name: name, email: email }; + onAdd(contact); + }; + + return ( +
+ + + +
+ ); +}; diff --git a/01 Testing components/src/myComponent/myComponent.spec.tsx b/01 Testing components/src/myComponent/myComponent.spec.tsx index 7fc5f29..54ce53e 100644 --- a/01 Testing components/src/myComponent/myComponent.spec.tsx +++ b/01 Testing components/src/myComponent/myComponent.spec.tsx @@ -1,31 +1,37 @@ import * as React from 'react'; // import { render, cleanup } from '@testing-library/react'; -import { render, fireEvent, waitForElement, act, RenderResult } from '@testing-library/react'; -import * as myApi from '../myApi'; -import { MyComponent, Props } from './myComponent'; +import { + render, + fireEvent, + waitForElement, + act, + RenderResult, +} from '@testing-library/react'; +import * as myFruitApi from '../myApi/myFruitApi'; +import { MyComponent } from './myComponent'; jest.mock('./myFruits.tsx', () => ({ MyFruits: () =>
, })); -const baseProps: Props = { - nameFromProps: null, -} +// const baseProps: Props = { +// nameFromProps: null, +// } //afterEach(cleanup); describe('My component', () => { - let props: Props; - beforeEach(() => { - props = {...baseProps}; - }); + // let props: Props; + // beforeEach(() => { + // props = {...baseProps}; + // }); it('should display the title provided', () => { // Arrange const name = 'Title'; // Act - const { getByText } = render(); + const { getByText } = render(); // Assert const element = getByText('Hello Title!'); @@ -48,7 +54,7 @@ describe('My component', () => { // Arrange // Act - const { getByTestId, getAllByText } = render(); + const { getByTestId, getAllByText } = render(); // const xxlabelElement = getByText(''); // https://testing-library.com/docs/dom-testing-library/api-queries const elementsWithEmptyText = getAllByText(''); const labelElement = getByTestId('userName-label'); @@ -62,7 +68,7 @@ describe('My component', () => { it('should update username label when the input changes', () => { // Arrange // Act - const { getByTestId } = render(); + const { getByTestId } = render(); const labelElement = getByTestId('userName-label'); const inputElement = getByTestId('userName-input') as HTMLInputElement; @@ -77,15 +83,15 @@ describe('My component', () => { xit('should display the list of fruits after resolving the api call on initialization', async () => { // Arrange const getListOfFruitMock = jest - .spyOn(myApi, 'getListOfFruit') + .spyOn(myFruitApi, 'getListOfFruit') .mockResolvedValue(['Melon', 'Apple', 'Pear']); // Act let wrapper: RenderResult = null; - await act(async() => { - wrapper = render(); + await act(async () => { + wrapper = render(); }); - const {getByText} = wrapper; + const { getByText } = wrapper; await waitForElement(() => getByText('Melon')); const melonElement = getByText('Melon'); const appleElement = getByText('Apple'); @@ -97,4 +103,4 @@ describe('My component', () => { expect(appleElement).not.toBeUndefined(); expect(pearElement).not.toBeUndefined(); }); -}); \ No newline at end of file +}); diff --git a/01 Testing components/src/myComponent/myComponent.tsx b/01 Testing components/src/myComponent/myComponent.tsx index c66da6e..3bf9c61 100644 --- a/01 Testing components/src/myComponent/myComponent.tsx +++ b/01 Testing components/src/myComponent/myComponent.tsx @@ -1,25 +1,99 @@ -import * as React from 'react'; -import { MyFruits } from './myFruits'; +import React, { useState, useEffect, FC } from "react"; +import { Dispatch } from "redux"; +import { useSelector, useDispatch } from "react-redux"; -export interface Props { - nameFromProps: string; -} +import { + getContactList, + deleteContact, + addContact, + updateContact, +} from "../myApi"; +import { + loadAllContacts, + contactUpdate, + contactCreate, + contactDelete, +} from "../store/contact/actions"; +import { MyContact, MyAddContact, MyLoading } from "../myComponent"; +import { + ContactActionTypes, + Contact as ContactType, +} from "../store/contact/types"; +import { RootState } from "../store/contact/index"; -export const MyComponent: React.FunctionComponent = props => { - const { nameFromProps } = props; - const [userName, setUserName] = React.useState(''); +export const MyComponent: FC = () => { + const dispatch = useDispatch>(); + const contacts = useSelector((state: RootState) => state.contactReducer); + + const [loading, setLoading] = useState(false); + const [error, setError] = useState(false); + + // useEffect loads all contacts. + useEffect(() => { + setLoading(true); + getContactList() + .then((u) => dispatch(loadAllContacts(u.data))) + .catch(() => setError(true)) + .finally(() => setLoading(false)); + }, []); + + // If there is an error display simple message + if (error === true) { + return

Something went wrong...

; + } + + const handleChange = (contact: ContactType) => { + setLoading(true); + updateContact(contact) + .then(() => dispatch(contactUpdate(contact))) + .catch(() => setError(true)) + .finally(() => setLoading(false)); + }; + + const handleDelete = (contact: ContactType) => { + setLoading(true); + deleteContact(contact) + .then(() => dispatch(contactDelete(contact))) + .catch(() => setError(true)) + .finally(() => setLoading(false)); + }; + + const handleAdd = (contact: ContactType) => { + setLoading(true); + addContact(contact) + .then((u) => dispatch(contactCreate(u.data))) + .catch(() => setError(true)) + .finally(() => setLoading(false)); + }; return ( - <> -

Hello {nameFromProps}!

-

Username

-

{userName}

- setUserName(e.target.value)} - /> - - +
+ +
); -}; \ No newline at end of file +}; diff --git a/01 Testing components/src/myComponent/myContact.tsx b/01 Testing components/src/myComponent/myContact.tsx new file mode 100644 index 0000000..39bc650 --- /dev/null +++ b/01 Testing components/src/myComponent/myContact.tsx @@ -0,0 +1,85 @@ +import React, { useState, FC } from "react"; +import { Contact as ContactType } from "../store/contact/types"; + +export interface Props { + contact: ContactType; + onDelete(contact: ContactType): void; + onChange(contact: ContactType): void; +} + +export const MyContact: FC = ({ contact, onDelete, onChange }) => { + const [showEdit, setShowEdit] = useState(false); + const [email, setEmail] = useState(""); + + const handleChange = (e: React.MouseEvent) => { + e.preventDefault(); + setShowEdit(true); + }; + + const handleSave = ( + e: React.MouseEvent, + contact: ContactType + ) => { + e.preventDefault(); + setShowEdit(false); + const updatedContact = { ...contact, email: email }; + onChange(updatedContact); + }; + + const handleDelete = ( + e: React.MouseEvent, + contact: ContactType + ) => { + e.preventDefault(); + onDelete(contact); + }; + + // Edit form let change email. + // By default edit form is hidden. + // It appers when "change email" button is clicked. + let editForm; + if (showEdit === true) { + editForm = ( + + + setEmail(e.target.value)} + /> + + + + + + ); + } + return ( + + + {contact.name} + {contact.email} + + + + + + + + {editForm} + + ); +}; diff --git a/01 Testing components/src/myComponent/myFruits.tsx b/01 Testing components/src/myComponent/myFruits.tsx index 591b96f..6cbb54b 100644 --- a/01 Testing components/src/myComponent/myFruits.tsx +++ b/01 Testing components/src/myComponent/myFruits.tsx @@ -1,9 +1,9 @@ -import * as React from 'react'; -import { getListOfFruit } from '../myApi'; +import React, { useEffect} from 'react'; +import { getListOfFruit } from '../myApi/myFruitApi'; -export const MyFruits = (props) => { +export const MyFruits = () => { const [fruits, setFruits] = React.useState([]); - React.useEffect(() => { + useEffect(() => { getListOfFruit().then(setFruits); }, []); @@ -11,8 +11,10 @@ export const MyFruits = (props) => { <>

Fruits gallore

{fruits.map((fruit, index) => ( -
  • {fruit}
  • +
  • + {fruit} +
  • ))} - ) -} \ No newline at end of file + ); +}; diff --git a/01 Testing components/src/myComponent/myLoading.tsx b/01 Testing components/src/myComponent/myLoading.tsx new file mode 100644 index 0000000..f3296cc --- /dev/null +++ b/01 Testing components/src/myComponent/myLoading.tsx @@ -0,0 +1,23 @@ +import React, { FC } from "react"; +import PacmanLoader from "react-spinners/PacmanLoader"; + +export interface Props { + hidden: boolean; + children: any; +} + +export const MyLoading: FC = ({ children, hidden }) => { + // Children are rendered when the loader is not displayed + if (hidden === true) { + return children || null; + } + + return ( +
    +
    + +
    +

    Loading...

    +
    + ); +}; diff --git a/01 Testing components/src/store/contact/actions.ts b/01 Testing components/src/store/contact/actions.ts new file mode 100644 index 0000000..7fa47a8 --- /dev/null +++ b/01 Testing components/src/store/contact/actions.ts @@ -0,0 +1,24 @@ +import { + Contact, + USERS_UPDATE, + USER_UPDATE, + USER_CREATE, + USER_DELETE, + ContactActionTypes, +} from "./types"; + +export function loadAllContacts(contacts: Array): ContactActionTypes { + return { type: USERS_UPDATE, payload: contacts }; +} + +export function contactUpdate(contact: Contact): ContactActionTypes { + return { type: USER_UPDATE, payload: { contact } }; +} + +export function contactCreate(contact: Contact): ContactActionTypes { + return { type: USER_CREATE, payload: { contact } }; +} + +export function contactDelete(contact: Contact): ContactActionTypes { + return { type: USER_DELETE, payload: { contact } }; +} diff --git a/01 Testing components/src/store/contact/index.ts b/01 Testing components/src/store/contact/index.ts new file mode 100644 index 0000000..007e606 --- /dev/null +++ b/01 Testing components/src/store/contact/index.ts @@ -0,0 +1,8 @@ +import { combineReducers } from "redux"; +import { contactReducer } from "./reducers"; + +export const rootReducer = combineReducers({ contactReducer }); + +export type RootState = ReturnType; + +export default rootReducer \ No newline at end of file diff --git a/01 Testing components/src/store/contact/reducers.ts b/01 Testing components/src/store/contact/reducers.ts new file mode 100644 index 0000000..d494b8b --- /dev/null +++ b/01 Testing components/src/store/contact/reducers.ts @@ -0,0 +1,32 @@ +import { + Contact, + ContactActionTypes, + USERS_UPDATE, + USER_UPDATE, + USER_CREATE, + USER_DELETE, +} from "./types"; + +export function contactReducer(state: Array = [], action: ContactActionTypes) { + switch (action.type) { + case USERS_UPDATE: { + return action.payload; + } + case USER_UPDATE: { + const contacts = state.map((obj) => + action.payload.contact.id === obj.id ? action.payload.contact : obj + ); + return contacts; + } + case USER_DELETE: { + const contacts = state.filter((obj) => obj.id !== action.payload.contact.id); + + return contacts; + } + case USER_CREATE: { + return [...state, action.payload.contact]; + } + default: + return state; + } +} diff --git a/01 Testing components/src/store/contact/types.ts b/01 Testing components/src/store/contact/types.ts new file mode 100644 index 0000000..25515d1 --- /dev/null +++ b/01 Testing components/src/store/contact/types.ts @@ -0,0 +1,42 @@ +export interface Contact { + readonly id: string; + readonly name: string; + readonly email: string; +} + +export const USERS_UPDATE = "USERS_UPDATE"; +export const USER_UPDATE = "USER_UPDATE"; +export const USER_CREATE = "USER_CREATE"; +export const USER_DELETE = "USER_DELETE"; + +interface loadAllContactsAction { + type: typeof USERS_UPDATE; + payload: Array; +} + +interface contactUpdateAction { + type: typeof USER_UPDATE; + payload: { + contact: Contact; + }; +} + +interface contactCreateAction { + type: typeof USER_CREATE; + payload: { + contact: Contact; + }; +} + +interface contactDeleteAction { + type: typeof USER_DELETE; + payload: { + contact: Contact; + }; +} + +export type ContactActionTypes = + | loadAllContactsAction + | contactUpdateAction + | contactCreateAction + | contactDeleteAction; diff --git a/01 Testing components/src/styles/_contact-add.scss b/01 Testing components/src/styles/_contact-add.scss new file mode 100644 index 0000000..e1d5fa4 --- /dev/null +++ b/01 Testing components/src/styles/_contact-add.scss @@ -0,0 +1,37 @@ +@import "./mixin"; + +.contact-add { + width: 100%; + position: relative; + &__btn { + @include btn(); + width: 70px; + background-color: #36b03c; + font-size: 20px; + padding: 15px 10px; + margin-top: 20px; + &:focus { + -webkit-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + -moz-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + } + &:hover { + background-color: #37b93d; + } + } + &__input { + @include input(); + width: 20%; + font-size: 20px; + padding: 10px; + margin: 10px; + &:focus { + -webkit-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + -moz-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + border: 2px solid #a5cda5; + background-color: #e9f3e9; + color: #428c42; + } + } +} diff --git a/01 Testing components/src/styles/_contact-loading.scss b/01 Testing components/src/styles/_contact-loading.scss new file mode 100644 index 0000000..2d684c7 --- /dev/null +++ b/01 Testing components/src/styles/_contact-loading.scss @@ -0,0 +1,17 @@ +.contact-loading { + margin: 0; + border: 0; + padding: 0; + display: inline-block; + vertical-align: middle; + white-space: normal; + background: none; + outline: none; + &__title { + margin-top: 20px; + } + &__spinner { + height: 110px; + margin: 20px; + } +} \ No newline at end of file diff --git a/01 Testing components/src/styles/_contact-row.scss b/01 Testing components/src/styles/_contact-row.scss new file mode 100644 index 0000000..221c6bd --- /dev/null +++ b/01 Testing components/src/styles/_contact-row.scss @@ -0,0 +1,45 @@ +@import "./mixin"; + +.contact-row { + padding-top: 7px; + &__input { + @include input(); + width: auto; + font-size: 15px; + padding: 3px; + &:focus { + -webkit-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + -moz-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + border: 2px solid #eec596; + background-color: #f0d6b8; + color: #8c7242; + } + } + &__input{ + max-width: 80%; + } + &__btn { + @include btn(); + margin-left: auto; + width: auto; + background-color: #e0841a; + font-size: 15px; + padding: 5px 5px; + &.delete { + background-color: #d63619; + &:hover { + background-color: #ec634a; + } + } + + &:focus { + -webkit-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + -moz-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + } + &:hover { + background-color: #faa13b; + } + } +} diff --git a/01 Testing components/src/styles/_container.scss b/01 Testing components/src/styles/_container.scss new file mode 100644 index 0000000..7660679 --- /dev/null +++ b/01 Testing components/src/styles/_container.scss @@ -0,0 +1,21 @@ +.container { + padding-top: 20px; + background: #fafafa; + display: flex; + margin: 0; + min-width: 500px; + max-width: 80%; + text-align: center; + align-items: center; + flex-direction: column; + justify-content: center; + border-radius: 5px; + box-shadow: 0 0 5px rgba(25, 25, 25, 0.25); + padding: 3rem; +} + +.container-title { + margin-bottom: auto; + margin-top: 0; + line-height: 1; +} diff --git a/01 Testing components/src/styles/_mixin.scss b/01 Testing components/src/styles/_mixin.scss new file mode 100644 index 0000000..5246b6c --- /dev/null +++ b/01 Testing components/src/styles/_mixin.scss @@ -0,0 +1,18 @@ +@mixin btn { + font-family: "Varela Round", sans-serif; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + letter-spacing: 2px; + outline: none; +} + +@mixin input { + background-color: #efefef; + color: #666; + border: 2px solid #ddd; + border-radius: 5px; + box-sizing: border-box; + outline: none; +} \ No newline at end of file diff --git a/01 Testing components/src/styles/style.css b/01 Testing components/src/styles/style.css new file mode 100644 index 0000000..54663d1 --- /dev/null +++ b/01 Testing components/src/styles/style.css @@ -0,0 +1,195 @@ +@import url("https://fonts.googleapis.com/css2?family=Varela+Round&display=swap"); +.container { + padding-top: 20px; + background: #fafafa; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + margin: 0; + min-width: 500px; + max-width: 80%; + text-align: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + border-radius: 5px; + -webkit-box-shadow: 0 0 5px rgba(25, 25, 25, 0.25); + box-shadow: 0 0 5px rgba(25, 25, 25, 0.25); + padding: 3rem; +} + +.container-title { + margin-bottom: auto; + margin-top: 0; + line-height: 1; +} + +.contact-add { + width: 100%; + position: relative; +} + +.contact-add__btn { + font-family: "Varela Round", sans-serif; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + letter-spacing: 2px; + outline: none; + width: 70px; + background-color: #36b03c; + font-size: 20px; + padding: 15px 10px; + margin-top: 20px; +} + +.contact-add__btn:focus { + -webkit-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); +} + +.contact-add__btn:hover { + background-color: #37b93d; +} + +.contact-add__input { + background-color: #efefef; + color: #666; + border: 2px solid #ddd; + border-radius: 5px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + outline: none; + width: 20%; + font-size: 20px; + padding: 10px; + margin: 10px; +} + +.contact-add__input:focus { + -webkit-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + border: 2px solid #a5cda5; + background-color: #e9f3e9; + color: #428c42; +} + +.contact-loading { + margin: 0; + border: 0; + padding: 0; + display: inline-block; + vertical-align: middle; + white-space: normal; + background: none; + outline: none; +} + +.contact-loading__title { + margin-top: 20px; +} + +.contact-loading__spinner { + height: 110px; + margin: 20px; +} + +.contact-row { + padding-top: 7px; +} + +.contact-row__input { + background-color: #efefef; + color: #666; + border: 2px solid #ddd; + border-radius: 5px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + outline: none; + width: auto; + font-size: 15px; + padding: 3px; +} + +.contact-row__input:focus { + -webkit-box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 10px 2px rgba(204, 204, 204, 0.9); + border: 2px solid #eec596; + background-color: #f0d6b8; + color: #8c7242; +} + +.contact-row__input { + max-width: 80%; +} + +.contact-row__btn { + font-family: "Varela Round", sans-serif; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + letter-spacing: 2px; + outline: none; + margin-left: auto; + width: auto; + background-color: #e0841a; + font-size: 15px; + padding: 5px 5px; +} + +.contact-row__btn.delete { + background-color: #d63619; +} + +.contact-row__btn.delete:hover { + background-color: #ec634a; +} + +.contact-row__btn:focus { + -webkit-box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); + box-shadow: 0px 0px 15px 5px rgba(204, 204, 204, 0.9); +} + +.contact-row__btn:hover { + background-color: #faa13b; +} + +html, +body { + width: 100%; + height: auto; +} + +body { + font-family: "Varela Round", sans-serif; + color: #2e2e2e; + font-size: 18px; + line-height: 1.5; + background: #b3b3ff; + margin: 0; + padding: 0; +} + +#root { + padding-top: 15px; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + min-width: 600px; +} +/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/01 Testing components/src/styles/style.css.map b/01 Testing components/src/styles/style.css.map new file mode 100644 index 0000000..51032f4 --- /dev/null +++ b/01 Testing components/src/styles/style.css.map @@ -0,0 +1,16 @@ +{ + "version": 3, + "mappings": "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", + "sources": [ + "style.scss", + "_container.scss", + "_contact-add.scss", + "_mixin.scss", + "_contact-loading.scss", + "_contact-row.scss", + "_mixin.scss", + "_mixin.scss" + ], + "names": [], + "file": "style.css" +} \ No newline at end of file diff --git a/01 Testing components/src/styles/style.scss b/01 Testing components/src/styles/style.scss new file mode 100644 index 0000000..c868296 --- /dev/null +++ b/01 Testing components/src/styles/style.scss @@ -0,0 +1,29 @@ +@import url("https://fonts.googleapis.com/css2?family=Varela+Round&display=swap"); +@import "./container"; +@import "./contact-add"; +@import "./contact-loading"; +@import "./contact-row"; +@import "./mixin"; + +html, +body { + width: 100%; + height: auto; +} + +body { + font-family: "Varela Round", sans-serif; + color: #2e2e2e; + font-size: 18px; + line-height: 1.5; + background: #b3b3ff; + margin: 0; + padding: 0; +} +#root { + padding-top: 15px; + justify-content: center; + display: flex; + align-items: center; + min-width: 600px; +} From 4490f1006bace9042669ba603b5cbe2f8f34fd88 Mon Sep 17 00:00:00 2001 From: agaskrobot Date: Thu, 9 Apr 2020 20:26:34 +0200 Subject: [PATCH 06/17] Delete Fruits component --- .../src/myApi/myBackEndApiEndpoint.ts | 12 ----------- 01 Testing components/src/myApi/myFruitApi.ts | 15 -------------- .../src/myComponent/myAddContact.tsx | 2 +- .../src/myComponent/myComponent.spec.tsx | 5 ++--- .../src/myComponent/myFruits.tsx | 20 ------------------- 5 files changed, 3 insertions(+), 51 deletions(-) delete mode 100644 01 Testing components/src/myApi/myBackEndApiEndpoint.ts delete mode 100644 01 Testing components/src/myApi/myFruitApi.ts delete mode 100644 01 Testing components/src/myComponent/myFruits.tsx diff --git a/01 Testing components/src/myApi/myBackEndApiEndpoint.ts b/01 Testing components/src/myApi/myBackEndApiEndpoint.ts deleted file mode 100644 index 1457042..0000000 --- a/01 Testing components/src/myApi/myBackEndApiEndpoint.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const getFruits = (_url: string) => { - return Promise.resolve([ - 'grape', - 'pineapple', - 'watermelon', - 'orange', - 'lemon', - 'strawberry', - 'cherry', - 'peach', - ]); -} \ No newline at end of file diff --git a/01 Testing components/src/myApi/myFruitApi.ts b/01 Testing components/src/myApi/myFruitApi.ts deleted file mode 100644 index 988d0c5..0000000 --- a/01 Testing components/src/myApi/myFruitApi.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as BEApi from './myBackEndApiEndpoint'; - -export const getListOfFruit = (): Promise => { - return BEApi.getFruits('http://fruityfruit.com') - .then(resolveFruits) - .catch(handleError); -} - -const resolveFruits = (fruits: string[]) => { - return fruits; -} - -const handleError = () => { - throw new Error('Where is my fruit???'); -} \ No newline at end of file diff --git a/01 Testing components/src/myComponent/myAddContact.tsx b/01 Testing components/src/myComponent/myAddContact.tsx index 182c114..f00e3a9 100644 --- a/01 Testing components/src/myComponent/myAddContact.tsx +++ b/01 Testing components/src/myComponent/myAddContact.tsx @@ -9,7 +9,7 @@ export const MyAddContact: FC = ({ onAdd }) => { const [name, setName] = useState(""); const [email, setEmail] = useState(""); - // Initial id is an epty string which is gonna be change when api is called + // Initial id is an empty string which is gonna be change when api is called const handleAdd = () => { const contact = { id: '', name: name, email: email }; onAdd(contact); diff --git a/01 Testing components/src/myComponent/myComponent.spec.tsx b/01 Testing components/src/myComponent/myComponent.spec.tsx index 54ce53e..66dee79 100644 --- a/01 Testing components/src/myComponent/myComponent.spec.tsx +++ b/01 Testing components/src/myComponent/myComponent.spec.tsx @@ -7,7 +7,7 @@ import { act, RenderResult, } from '@testing-library/react'; -import * as myFruitApi from '../myApi/myFruitApi'; +import * as myContactApi from '../myApi/myContactApi'; import { MyComponent } from './myComponent'; jest.mock('./myFruits.tsx', () => ({ @@ -83,8 +83,7 @@ describe('My component', () => { xit('should display the list of fruits after resolving the api call on initialization', async () => { // Arrange const getListOfFruitMock = jest - .spyOn(myFruitApi, 'getListOfFruit') - .mockResolvedValue(['Melon', 'Apple', 'Pear']); + .spyOn(myContactApi, 'getContactList') // Act let wrapper: RenderResult = null; diff --git a/01 Testing components/src/myComponent/myFruits.tsx b/01 Testing components/src/myComponent/myFruits.tsx deleted file mode 100644 index 6cbb54b..0000000 --- a/01 Testing components/src/myComponent/myFruits.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, { useEffect} from 'react'; -import { getListOfFruit } from '../myApi/myFruitApi'; - -export const MyFruits = () => { - const [fruits, setFruits] = React.useState([]); - useEffect(() => { - getListOfFruit().then(setFruits); - }, []); - - return ( - <> -

    Fruits gallore

    - {fruits.map((fruit, index) => ( -
  • - {fruit} -
  • - ))} - - ); -}; From 90eda8679d25763f3e1df42139dbc6190e8dd9db Mon Sep 17 00:00:00 2001 From: agaskrobot Date: Sat, 11 Apr 2020 12:22:43 +0200 Subject: [PATCH 07/17] Test for components, actions, reducers --- 01 Testing components/package.json | 8 +- 01 Testing components/src/index.tsx | 2 - 01 Testing components/src/myApi/index.ts | 1 - .../src/myApi/myContactApi.ts | 16 +-- .../src/myComponent/myAddContact.spec.tsx | 45 +++++++ .../src/myComponent/myAddContact.tsx | 16 ++- .../src/myComponent/myComponent.spec.tsx | 105 ---------------- .../src/myComponent/myComponent.tsx | 9 +- .../src/myComponent/myContact.spec.tsx | 77 ++++++++++++ .../src/myComponent/myContact.tsx | 21 ++-- .../src/myComponent/myLoading.spec.tsx | 29 +++++ .../src/myComponent/myLoading.tsx | 2 +- .../src/store/contact/actions.spec.ts | 53 ++++++++ .../src/store/contact/actions.ts | 18 +-- .../src/store/contact/reducers.spec.ts | 116 ++++++++++++++++++ .../src/store/contact/reducers.ts | 24 ++-- .../src/store/contact/types.ts | 28 ++--- .../src/styles/style.css.map | 2 +- 01 Testing components/src/styles/style.scss | 1 + 19 files changed, 400 insertions(+), 173 deletions(-) create mode 100644 01 Testing components/src/myComponent/myAddContact.spec.tsx create mode 100644 01 Testing components/src/myComponent/myContact.spec.tsx create mode 100644 01 Testing components/src/myComponent/myLoading.spec.tsx create mode 100644 01 Testing components/src/store/contact/actions.spec.ts create mode 100644 01 Testing components/src/store/contact/reducers.spec.ts diff --git a/01 Testing components/package.json b/01 Testing components/package.json index e94b3f0..22eff7b 100644 --- a/01 Testing components/package.json +++ b/01 Testing components/package.json @@ -33,6 +33,8 @@ "@babel/preset-env": "^7.9.5", "@babel/preset-react": "^7.9.4", "@testing-library/react": "^8.0.9", + "@testing-library/react-hooks": "^3.2.1", + "@types/enzyme": "^3.10.5", "@types/jest": "^24.9.1", "@types/react": "^16.9.33", "@types/react-dom": "^16.9.6", @@ -40,9 +42,13 @@ "awesome-typescript-loader": "^5.2.1", "babel-loader": "^8.1.0", "css-loader": "^3.5.1", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.2", "html-webpack-plugin": "^3.2.0", - "jest": "^24.8.0", + "jest": "^24.9.0", "node-sass": "^4.13.1", + "react-test-renderer": "^16.13.1", + "redux-mock-store": "^1.5.4", "rimraf": "^2.6.3", "sass-loader": "^8.0.2", "source-map-loader": "^0.2.4", diff --git a/01 Testing components/src/index.tsx b/01 Testing components/src/index.tsx index 9ca89b3..5f5f27f 100644 --- a/01 Testing components/src/index.tsx +++ b/01 Testing components/src/index.tsx @@ -12,9 +12,7 @@ const store = createStore(rootReducer) ReactDOM.render( - - , document.getElementById("root") ); diff --git a/01 Testing components/src/myApi/index.ts b/01 Testing components/src/myApi/index.ts index 24ab56a..4b61ed9 100644 --- a/01 Testing components/src/myApi/index.ts +++ b/01 Testing components/src/myApi/index.ts @@ -1,4 +1,3 @@ -export { getListOfFruit } from './myFruitApi'; export { getContactList, updateContact, diff --git a/01 Testing components/src/myApi/myContactApi.ts b/01 Testing components/src/myApi/myContactApi.ts index 8980624..ca56f41 100644 --- a/01 Testing components/src/myApi/myContactApi.ts +++ b/01 Testing components/src/myApi/myContactApi.ts @@ -1,18 +1,18 @@ -import axios from "axios/index"; -import { Contact as ContactType } from "../store/contact/types"; +import axios from 'axios/index'; +import { Contact as ContactType } from '../store/contact/types'; + +export const url = 'https://jsonplaceholder.typicode.com/users'; // Get all contacts -export const getContactList = () => - axios.get("https://jsonplaceholder.typicode.com/users"); +export const getContactList = () => axios.get(url); // Delete contact export const deleteContact = (contact: ContactType) => - axios.delete(`https://jsonplaceholder.typicode.com/users/${contact.id}`); + axios.delete(`${url}/${contact.id}`); // Update existing contact export const updateContact = (contact: ContactType) => - axios.put(`https://jsonplaceholder.typicode.com/users/${contact.id}`,contact); + axios.put(`${url}/${contact.id}`, contact); // Create new contact -export const addContact = (contact: ContactType) => - axios.post("https://jsonplaceholder.typicode.com/users", contact); +export const addContact = (contact: ContactType) => axios.post(url, contact); diff --git a/01 Testing components/src/myComponent/myAddContact.spec.tsx b/01 Testing components/src/myComponent/myAddContact.spec.tsx new file mode 100644 index 0000000..ec052ab --- /dev/null +++ b/01 Testing components/src/myComponent/myAddContact.spec.tsx @@ -0,0 +1,45 @@ +import * as React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { MyAddContact, Props } from './myAddContact'; +import Enzyme, { mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +Enzyme.configure({ adapter: new Adapter() }); + +const baseProps: Props = { + onAdd: jest.fn(), +}; + +describe('MyAddContact', () => { + let props: Props; + let appWrapper; + beforeEach(() => { + props = { ...baseProps }; + appWrapper = mount(); + }); + + it('Should render without errors', () => { + expect(appWrapper.length).toBe(1); + }); + + it('Set the email and name and trigger onAdd "', () => { + const { getByTestId } = render(); + + const inputElementName = getByTestId('add-input-name') as HTMLInputElement; + + const inputElementEmail = getByTestId( + 'add-input-email' + ) as HTMLInputElement; + + const buttonElementAdd = getByTestId('add-button-add') as HTMLButtonElement; + + fireEvent.change(inputElementName, { target: { value: 'John' } }); + fireEvent.change(inputElementEmail, { + target: { value: 'John@gmail.com' }, + }); + buttonElementAdd.click(); + + expect(inputElementName.value).toEqual('John'); + expect(inputElementEmail.value).toEqual('John@gmail.com'); + expect(props.onAdd).toBeCalledTimes(1); + }); +}); diff --git a/01 Testing components/src/myComponent/myAddContact.tsx b/01 Testing components/src/myComponent/myAddContact.tsx index f00e3a9..7047f67 100644 --- a/01 Testing components/src/myComponent/myAddContact.tsx +++ b/01 Testing components/src/myComponent/myAddContact.tsx @@ -1,13 +1,13 @@ -import React, { useState, FC } from "react"; -import { Contact as ContactType } from "../store/contact/types"; +import React, { useState, FC } from 'react'; +import { Contact as ContactType } from '../store/contact/types'; export interface Props { onAdd(contact: ContactType): void; } export const MyAddContact: FC = ({ onAdd }) => { - const [name, setName] = useState(""); - const [email, setEmail] = useState(""); + const [name, setName] = useState(''); + const [email, setEmail] = useState(''); // Initial id is an empty string which is gonna be change when api is called const handleAdd = () => { @@ -20,6 +20,7 @@ export const MyAddContact: FC = ({ onAdd }) => {
    diff --git a/01 Testing components/src/myComponent/myComponent.spec.tsx b/01 Testing components/src/myComponent/myComponent.spec.tsx index 66dee79..e69de29 100644 --- a/01 Testing components/src/myComponent/myComponent.spec.tsx +++ b/01 Testing components/src/myComponent/myComponent.spec.tsx @@ -1,105 +0,0 @@ -import * as React from 'react'; -// import { render, cleanup } from '@testing-library/react'; -import { - render, - fireEvent, - waitForElement, - act, - RenderResult, -} from '@testing-library/react'; -import * as myContactApi from '../myApi/myContactApi'; -import { MyComponent } from './myComponent'; - -jest.mock('./myFruits.tsx', () => ({ - MyFruits: () =>
    , -})); - -// const baseProps: Props = { -// nameFromProps: null, -// } - -//afterEach(cleanup); - -describe('My component', () => { - // let props: Props; - // beforeEach(() => { - // props = {...baseProps}; - // }); - - it('should display the title provided', () => { - // Arrange - const name = 'Title'; - - // Act - const { getByText } = render(); - - // Assert - const element = getByText('Hello Title!'); - expect(element).not.toBeNull(); - expect(element.tagName).toEqual('H1'); - }); - - // it('should display the person name using snapshot testing', () => { - // // Arrange - // const name = 'Fruity'; - - // // Act - // const { asFragment } = render(); - - // // Assert - // expect(asFragment()).toMatchSnapshot(); - // }); - - it('should display a label and input elements with empty userName value', () => { - // Arrange - - // Act - const { getByTestId, getAllByText } = render(); - // const xxlabelElement = getByText(''); // https://testing-library.com/docs/dom-testing-library/api-queries - const elementsWithEmptyText = getAllByText(''); - const labelElement = getByTestId('userName-label'); - const inputElement = getByTestId('userName-input') as HTMLInputElement; - - // Assert - expect(labelElement.textContent).toEqual(''); - expect(inputElement.value).toEqual(''); - }); - - it('should update username label when the input changes', () => { - // Arrange - // Act - const { getByTestId } = render(); - - const labelElement = getByTestId('userName-label'); - const inputElement = getByTestId('userName-input') as HTMLInputElement; - - fireEvent.change(inputElement, { target: { value: 'John' } }); - - // Assert - expect(labelElement.textContent).toEqual('John'); - expect(inputElement.value).toEqual('John'); - }); - - xit('should display the list of fruits after resolving the api call on initialization', async () => { - // Arrange - const getListOfFruitMock = jest - .spyOn(myContactApi, 'getContactList') - - // Act - let wrapper: RenderResult = null; - await act(async () => { - wrapper = render(); - }); - const { getByText } = wrapper; - await waitForElement(() => getByText('Melon')); - const melonElement = getByText('Melon'); - const appleElement = getByText('Apple'); - const pearElement = getByText('Pear'); - - // Assert - expect(getListOfFruitMock).toHaveBeenCalled(); - expect(melonElement).not.toBeUndefined(); - expect(appleElement).not.toBeUndefined(); - expect(pearElement).not.toBeUndefined(); - }); -}); diff --git a/01 Testing components/src/myComponent/myComponent.tsx b/01 Testing components/src/myComponent/myComponent.tsx index 3bf9c61..3e52df5 100644 --- a/01 Testing components/src/myComponent/myComponent.tsx +++ b/01 Testing components/src/myComponent/myComponent.tsx @@ -21,9 +21,10 @@ import { } from "../store/contact/types"; import { RootState } from "../store/contact/index"; -export const MyComponent: FC = () => { - const dispatch = useDispatch>(); +export const MyComponent: FC = () => { const contacts = useSelector((state: RootState) => state.contactReducer); + const dispatch = useDispatch>(); + const [loading, setLoading] = useState(false); const [error, setError] = useState(false); @@ -67,9 +68,9 @@ export const MyComponent: FC = () => { }; return ( -
    +