diff --git a/00 Base/config/webpack/base.js b/00 Base/config/webpack/base.js
index dff4c7b..d89604e 100644
--- a/00 Base/config/webpack/base.js
+++ b/00 Base/config/webpack/base.js
@@ -8,10 +8,7 @@ module.exports = merge(
{
context: helpers.resolveFromRootPath('src'),
resolve: {
- extensions: ['.js', '.ts', '.tsx'],
- modules: [
- 'node_modules'
- ]
+ extensions: ['.js', '.ts', '.tsx', '.css', '.scss', '.less'],
},
entry: {
app: ['./index.tsx'],
@@ -28,14 +25,18 @@ module.exports = merge(
babelCore: '@babel/core',
}
},
+ {
+ test: /\.css$/,
+ loaders: ['style-loader', 'css-loader'],
+ },
+ {
+ test: /\.s[ac]ss$/i,
+ loaders: ['style-loader', 'css-loader', 'sass-loader'],
+ },
{
test: /\.less$/,
- use: [
- { loader: 'style-loader' },
- { loader: 'css-loader' },
- { loader: 'less-loader' }
- ]
- }
+ use: ['style-loader', 'css-loader', 'less-loader'],
+ },
],
},
optimization: {
diff --git a/00 Base/package.json b/00 Base/package.json
index 2e1ce01..ed52777 100644
--- a/00 Base/package.json
+++ b/00 Base/package.json
@@ -13,10 +13,12 @@
"author": "arp82",
"license": "MIT",
"dependencies": {
+ "@material-ui/core": "^4.9.9",
+ "@material-ui/icons": "^4.9.1",
+ "axios": "^0.19.2",
+ "babel-polyfill": "^6.26.0",
"@babel/preset-react": "^7.9.4",
"@babel/preset-stage-0": "^7.8.3",
- "@material-ui/core": "^4.9.9",
- "axios": "^0.19.0",
"babel-core": "^7.0.0-bridge.0",
"identity-obj-proxy": "^3.0.0",
"jest-transform-css": "^2.0.0",
@@ -24,9 +26,10 @@
"react-dom": "^16.8.6",
"react-redux": "^7.2.0",
"react-router-dom": "^5.0.1",
- "react-test-renderer": "^16.13.1",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
+ "sass-loader": "^8.0.2",
+ "react-test-renderer": "^16.13.1",
"sinon": "^9.0.1",
"style-loader": "^1.1.3",
"ts-jest": "^25.3.1"
@@ -48,11 +51,17 @@
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"html-webpack-plugin": "^3.2.0",
- "jest": "^24.9.0",
+ "jest": "^24.8.0",
"less": "^3.11.1",
"less-loader": "^5.0.0",
+ "node-sass": "^4.13.1",
"react-addons-test-utils": "^15.6.2",
+ "redux-devtools-extension": "^2.13.8",
"redux-mock-store": "^1.5.4",
+ "rimraf": "^2.6.3",
+ "sass-loader": "^7.2.0",
+ "style-loader": "^1.1.3",
+ "ts-jest": "^24.0.2",
"typescript": "^3.5.2",
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2",
diff --git a/00 Base/src/app.tsx b/00 Base/src/app.tsx
index 93864a7..f234558 100644
--- a/00 Base/src/app.tsx
+++ b/00 Base/src/app.tsx
@@ -1,8 +1,9 @@
import * as React from 'react';
-import { MessagesSection } from './components';
+import { MyComponent } from './components/myComponent';
+import './styles.less';
-export const App: React.FunctionComponent = props => (
+export const App: React.FunctionComponent = (props) => (
-
+
);
diff --git a/00 Base/src/components/myComponent/index.ts b/00 Base/src/components/myComponent/index.ts
new file mode 100644
index 0000000..84cbb30
--- /dev/null
+++ b/00 Base/src/components/myComponent/index.ts
@@ -0,0 +1 @@
+export { MyComponent, Posts } from './myComponent';
diff --git a/00 Base/src/components/myComponent/myComponent.spec.tsx b/00 Base/src/components/myComponent/myComponent.spec.tsx
new file mode 100644
index 0000000..decd453
--- /dev/null
+++ b/00 Base/src/components/myComponent/myComponent.spec.tsx
@@ -0,0 +1,59 @@
+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 { Provider } from 'react-redux';
+import * as redux from 'react-redux';
+import { createStore } from 'redux';
+import Enzyme, { mount } from 'enzyme';
+import EnzymeAdapter from 'enzyme-adapter-react-16';
+import { fetchPosts } from '../../redux/actions/index';
+
+import reducer from '../../redux/reducers/index';
+jest.mock('./MyComponent.tsx', () => ({
+ MyComponent: () => ,
+}));
+
+const baseProps: Props = {
+ nameFromProps: null,
+};
+Enzyme.configure({ adapter: new EnzymeAdapter() });
+
+//afterEach(cleanup);
+describe('My component', () => {
+ let props: Props;
+ beforeEach(() => {
+ props = { ...baseProps };
+ });
+
+ const name = 'Title';
+ const getWrapper = (
+ mockStore = createStore(reducer, { posts: { posts: [] } })
+ ) =>
+ mount(
+
+
+
+ );
+
+ it('should display the title provided', () => {
+ const wrapper = getWrapper();
+ expect(wrapper.find('h1').text()).toEqual(`Hello ${name}!`);
+ });
+
+ it('should dispatch the get post action', () => {
+ const mockStore = createStore(reducer, { posts: { posts: [] } });
+ mockStore.dispatch = jest.fn();
+
+ const wrapper = getWrapper(mockStore);
+ wrapper.find('button').simulate('click');
+ expect(mockStore.dispatch).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/00 Base/src/components/myComponent/myComponent.tsx b/00 Base/src/components/myComponent/myComponent.tsx
new file mode 100644
index 0000000..c8e3ab6
--- /dev/null
+++ b/00 Base/src/components/myComponent/myComponent.tsx
@@ -0,0 +1,64 @@
+import * as React from 'react';
+
+import { useSelector, useDispatch } from 'react-redux';
+import { fetchPosts, deletePost, addPost } from './../../redux/actions/index';
+import './styles.less';
+
+import Button from '@material-ui/core/Button';
+import Chip from '@material-ui/core/Chip';
+
+import { MyInputComponent } from '../myInputComponent/myInputComponent';
+export interface Props {
+ nameFromProps: string;
+}
+export interface Posts {
+ id: Number;
+ userId: Number;
+ title: string;
+ body: string;
+}
+
+export const MyComponent: React.FunctionComponent = (props) => {
+ const { nameFromProps } = props;
+ const dispatch = useDispatch();
+ const posts = useSelector((state) => state.posts.posts);
+
+ return (
+ <>
+
+
Hello {nameFromProps}!
+
+
+ {!!posts
+ ? posts.map((el: Posts) => (
+ dispatch(deletePost(el.id))}
+ />
+ ))
+ : null}
+
+
+ dispatch(
+ addPost({
+ id: Math.floor(Math.random() * 1000),
+ userId: 1,
+ title: newPost,
+ body: newPost,
+ })
+ )
+ }
+ />
+
+ >
+ );
+};
diff --git a/00 Base/src/components/myComponent/styles.less b/00 Base/src/components/myComponent/styles.less
new file mode 100644
index 0000000..3ec5f71
--- /dev/null
+++ b/00 Base/src/components/myComponent/styles.less
@@ -0,0 +1,12 @@
+.container {
+ text-align: center;
+ .chip-container {
+ display: block;
+ position: relative;
+ margin-top: 25px;
+ .MuiChip-root {
+ margin-left: 5px;
+ margin-top: 5px;
+ }
+ }
+}
diff --git a/00 Base/src/components/myInputComponent/index.ts b/00 Base/src/components/myInputComponent/index.ts
new file mode 100644
index 0000000..3b0b11d
--- /dev/null
+++ b/00 Base/src/components/myInputComponent/index.ts
@@ -0,0 +1 @@
+export { MyInputComponent } from './myInputComponent';
\ No newline at end of file
diff --git a/00 Base/src/components/myInputComponent/myInputComponent.tsx b/00 Base/src/components/myInputComponent/myInputComponent.tsx
new file mode 100644
index 0000000..bd63ca7
--- /dev/null
+++ b/00 Base/src/components/myInputComponent/myInputComponent.tsx
@@ -0,0 +1,31 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import { Add } from '@material-ui/icons';
+import './styles.less';
+
+export interface Props {
+ addPost: Function;
+}
+
+export const MyInputComponent: React.FunctionComponent = (props) => {
+ const { addPost } = props;
+ const [name, setName] = React.useState('');
+
+ return (
+
+
setName(e.target.value)}
+ />
+ {!!name ? (
+ {
+ addPost(name);
+ setName('');
+ }}
+ />
+ ) : null}
+
+ );
+};
diff --git a/00 Base/src/components/myInputComponent/styles.less b/00 Base/src/components/myInputComponent/styles.less
new file mode 100644
index 0000000..0af11b8
--- /dev/null
+++ b/00 Base/src/components/myInputComponent/styles.less
@@ -0,0 +1,9 @@
+.input-component {
+ .MuiFormControl-root {
+ margin-top: 10px;
+ }
+ .MuiSvgIcon-root {
+ margin-top: 35px;
+ color: #00ff00;
+ }
+}
diff --git a/00 Base/src/index.tsx b/00 Base/src/index.tsx
index 1725370..2c35ff9 100644
--- a/00 Base/src/index.tsx
+++ b/00 Base/src/index.tsx
@@ -1,16 +1,14 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
-import { App } from './app';
-import { Provider } from 'react-redux'
-import { createStore, compose, applyMiddleware } from 'redux'
-import {MessagesReducer} from './redux/reducers/MessagesReducer'
-import thunk from 'redux-thunk'
+import { Provider } from 'react-redux';
+import store from './redux/store';
-const MessagesStore = createStore(MessagesReducer,compose(applyMiddleware(thunk)));
+import { App } from './app';
+import 'babel-polyfill'
ReactDOM.render(
-
+
-,
-
- document.getElementById('root'));
+ ,
+ document.getElementById('root')
+);
diff --git a/00 Base/src/myApi/index.ts b/00 Base/src/myApi/index.ts
new file mode 100644
index 0000000..3cbca37
--- /dev/null
+++ b/00 Base/src/myApi/index.ts
@@ -0,0 +1 @@
+export { getListOfFruit, getPosts } from './myApi';
diff --git a/00 Base/src/myApi/myApi.spec.tsx b/00 Base/src/myApi/myApi.spec.tsx
new file mode 100644
index 0000000..49dbe83
--- /dev/null
+++ b/00 Base/src/myApi/myApi.spec.tsx
@@ -0,0 +1,19 @@
+import { getListOfFruit, getPosts } from './myApi';
+
+describe('myApi tests', () => {
+ it('getListOfFruit should return an array of string', () => {
+ expect(getListOfFruit()).resolves.toEqual([
+ 'grape',
+ 'pineapple',
+ 'watermelon',
+ 'orange',
+ 'lemon',
+ 'strawberry',
+ 'cherry',
+ 'peach',
+ ]);
+ });
+ it('should fecth some post from the api', () => {
+ expect(getPosts()).rejects.toThrowError('Error fetching');
+ });
+});
diff --git a/00 Base/src/myApi/myApi.ts b/00 Base/src/myApi/myApi.ts
new file mode 100644
index 0000000..8ebc456
--- /dev/null
+++ b/00 Base/src/myApi/myApi.ts
@@ -0,0 +1,30 @@
+import * as BEApi from './myBackEndApiEndpoint';
+import axios from 'axios';
+import { Posts } from '../components/myComponent/index';
+
+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???');
+};
+export const getPosts = (): Promise => {
+ return axios
+ .get('https://jsonplaceholder.typicode.com/posts')
+ .then(resolvePosts)
+ .catch(handlePostError);
+};
+const resolvePosts = (posts: any) => {
+ console.log('resolvePosts -> posts', posts);
+ return posts.data;
+};
+const handlePostError = () => {
+ throw new Error('Error fetching');
+};
diff --git a/00 Base/src/myApi/myBackEndApiEndpoint.ts b/00 Base/src/myApi/myBackEndApiEndpoint.ts
new file mode 100644
index 0000000..1457042
--- /dev/null
+++ b/00 Base/src/myApi/myBackEndApiEndpoint.ts
@@ -0,0 +1,12 @@
+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/00 Base/src/redux/actions/fruitsActions.tsx b/00 Base/src/redux/actions/fruitsActions.tsx
new file mode 100644
index 0000000..81d68bf
--- /dev/null
+++ b/00 Base/src/redux/actions/fruitsActions.tsx
@@ -0,0 +1,28 @@
+import { GET_ALL_FRUITS, DELETE_FRUIT, ADD_FRUIT } from './../constants';
+import { getListOfFruit } from '../../myApi/index';
+
+export const getAllFruits = (fruits: String[]) => {
+ return {
+ type: GET_ALL_FRUITS,
+ payload: fruits,
+ };
+};
+export const fetchfruits = () => {
+ return (dispatch) => {
+ return getListOfFruit().then((res) => dispatch(getAllFruits(res)));
+ };
+};
+
+export const deleteFruit = (name: string) => {
+ return {
+ type: DELETE_FRUIT,
+ payload: name,
+ };
+};
+
+export const addFruit = (name: string) => {
+ return {
+ type: ADD_FRUIT,
+ payload: name,
+ };
+};
diff --git a/00 Base/src/redux/actions/index.ts b/00 Base/src/redux/actions/index.ts
new file mode 100644
index 0000000..af7b50e
--- /dev/null
+++ b/00 Base/src/redux/actions/index.ts
@@ -0,0 +1,7 @@
+export {
+ addFruit,
+ deleteFruit,
+ fetchfruits,
+ getAllFruits,
+} from './fruitsActions';
+export { fetchPosts, deletePost,addPost } from './postActions';
diff --git a/00 Base/src/redux/actions/postActions.spec.tsx b/00 Base/src/redux/actions/postActions.spec.tsx
new file mode 100644
index 0000000..8322e8d
--- /dev/null
+++ b/00 Base/src/redux/actions/postActions.spec.tsx
@@ -0,0 +1,42 @@
+import { addPost, deletePost, fetchPosts } from './index';
+import { ADD_POST, DELETE_POST, GET_POST_FROM_API } from '../constants';
+import { getPostFromApi } from './postActions';
+
+describe('actions', () => {
+ const testPost = {
+ id: 7,
+ userId: 1,
+ title: 'newPost',
+ body: 'newPost',
+ };
+ const testPost2 = {
+ id: 8,
+ userId: 1,
+ title: 'newPost2',
+ body: 'newPost2',
+ };
+ it('should create an action to add a post', () => {
+ const expectedAction = {
+ type: ADD_POST,
+ payload: testPost,
+ };
+ expect(addPost(testPost)).toEqual(expectedAction);
+ });
+
+ it('should create an action to delete a post', () => {
+ const expectedAction = {
+ type: DELETE_POST,
+ payload: testPost.id,
+ };
+ expect(deletePost(testPost.id)).toEqual(expectedAction);
+ });
+
+ it('should create an action to get all posts', () => {
+ const arrayOfPosts=[testPost,testPost2]
+ const expectedAction = {
+ type: GET_POST_FROM_API,
+ payload: arrayOfPosts,
+ };
+ expect(getPostFromApi(arrayOfPosts)).toEqual(expectedAction);
+ });
+});
diff --git a/00 Base/src/redux/actions/postActions.tsx b/00 Base/src/redux/actions/postActions.tsx
new file mode 100644
index 0000000..7831de9
--- /dev/null
+++ b/00 Base/src/redux/actions/postActions.tsx
@@ -0,0 +1,29 @@
+import { GET_POST_FROM_API, DELETE_POST, ADD_POST } from './../constants';
+import { getPosts } from '../../myApi/index';
+import { Posts } from '../../components/myComponent/index';
+
+export const getPostFromApi = (posts: Posts[]) => {
+ return {
+ type: GET_POST_FROM_API,
+ payload: posts,
+ };
+};
+export const fetchPosts = () => {
+ return (dispatch) => {
+ return getPosts().then((res) => dispatch(getPostFromApi(res)));
+ };
+};
+
+export const deletePost = (id: Number) => {
+ return {
+ type: DELETE_POST,
+ payload: id,
+ };
+};
+
+export const addPost = (post: Posts) => {
+ return {
+ type: ADD_POST,
+ payload: post,
+ };
+};
diff --git a/00 Base/src/redux/constants.ts b/00 Base/src/redux/constants.ts
new file mode 100644
index 0000000..1e7fd44
--- /dev/null
+++ b/00 Base/src/redux/constants.ts
@@ -0,0 +1,7 @@
+export const GET_ALL_FRUITS = 'GET_ALL_FRUITS';
+export const DELETE_FRUIT = 'DELETE_FRUIT';
+export const ADD_FRUIT = 'ADD_FRUIT';
+
+export const GET_POST_FROM_API = 'GET_POST_FROM_API';
+export const DELETE_POST = 'DELETE_POST';
+export const ADD_POST = 'ADD_POST';
diff --git a/00 Base/src/redux/reducers/fruitReducer.tsx b/00 Base/src/redux/reducers/fruitReducer.tsx
new file mode 100644
index 0000000..e260a37
--- /dev/null
+++ b/00 Base/src/redux/reducers/fruitReducer.tsx
@@ -0,0 +1,31 @@
+import { GET_ALL_FRUITS, DELETE_FRUIT, ADD_FRUIT } from './../constants';
+const initialState = {
+ fruitList: [],
+};
+export default function (state = initialState, action) {
+ switch (action.type) {
+ case GET_ALL_FRUITS: {
+ return {
+ ...state,
+ fruitList: action.payload,
+ };
+ }
+ case DELETE_FRUIT: {
+ return {
+ ...state,
+ fruitList: state.fruitList.filter((e: string) => e !== action.payload),
+ };
+ }
+ case ADD_FRUIT: {
+ return {
+ ...state,
+ fruitList: state.fruitList.concat([action.payload]),
+ };
+ }
+ default: {
+ return {
+ ...state,
+ };
+ }
+ }
+}
diff --git a/00 Base/src/redux/reducers/index.ts b/00 Base/src/redux/reducers/index.ts
new file mode 100644
index 0000000..2026315
--- /dev/null
+++ b/00 Base/src/redux/reducers/index.ts
@@ -0,0 +1,8 @@
+import { combineReducers } from 'redux';
+import fruitsReducer from './fruitReducer';
+import postReducer from './postReducer'
+
+export default combineReducers({
+ fruits: fruitsReducer,
+ posts: postReducer
+});
diff --git a/00 Base/src/redux/reducers/postReducer.spec.tsx b/00 Base/src/redux/reducers/postReducer.spec.tsx
new file mode 100644
index 0000000..2cfeed2
--- /dev/null
+++ b/00 Base/src/redux/reducers/postReducer.spec.tsx
@@ -0,0 +1,86 @@
+import reducer from './postReducer';
+import {
+ ADD_FRUIT,
+ DELETE_FRUIT,
+ GET_POST_FROM_API,
+ ADD_POST,
+ DELETE_POST,
+} from '../constants';
+
+const initialState = {
+ posts: [],
+};
+
+describe('post reducer', () => {
+ const testPost = {
+ userId: 1,
+ id: 1,
+ title:
+ 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
+ body:
+ 'quia et suscipit suscipit recusandae consequuntur expedita et cum reprehenderit molestiae ut ut quas totam nostrum rerum est autem sunt rem eveniet architecto',
+ };
+ const testPost2 = {
+ userId: 2,
+ id: 2,
+ title: 'another Title',
+ body: 'another Title',
+ };
+ it('should return the initial state', () => {
+ expect(reducer(undefined, {})).toEqual({
+ posts: [],
+ });
+ });
+
+ it('should handle ADD_POST', () => {
+ expect(
+ reducer(initialState, {
+ type: ADD_POST,
+ payload: testPost,
+ })
+ ).toEqual({
+ posts: [testPost],
+ });
+
+ expect(
+ reducer(
+ {
+ posts: [testPost],
+ },
+ {
+ type: ADD_POST,
+ payload: {
+ id: 7,
+ userId: 1,
+ title: 'newPost',
+ body: 'newPost',
+ },
+ }
+ )
+ ).toEqual({
+ posts: [
+ testPost,
+ {
+ id: 7,
+ userId: 1,
+ title: 'newPost',
+ body: 'newPost',
+ },
+ ],
+ });
+ });
+
+ it('should handle DELETE_POST', () => {
+ expect(
+ reducer(
+ { posts: [testPost, testPost2] },
+ {
+ type: DELETE_POST,
+ payload: testPost2.id,
+ }
+ )
+ ).toEqual({
+ posts: [testPost],
+ });
+ });
+});
diff --git a/00 Base/src/redux/reducers/postReducer.tsx b/00 Base/src/redux/reducers/postReducer.tsx
new file mode 100644
index 0000000..e037c8f
--- /dev/null
+++ b/00 Base/src/redux/reducers/postReducer.tsx
@@ -0,0 +1,32 @@
+import { GET_POST_FROM_API, DELETE_POST, ADD_POST } from './../constants';
+const initialState = {
+ posts: [],
+};
+export default function (state = initialState, action) {
+ switch (action.type) {
+ case GET_POST_FROM_API: {
+ return {
+ ...state,
+ posts: action.payload,
+ };
+ }
+ case DELETE_POST: {
+ return {
+ ...state,
+ posts: state.posts.filter((e) => e.id !== action.payload),
+ };
+ }
+
+ case ADD_POST: {
+ return {
+ ...state,
+ posts: state.posts.concat([action.payload]),
+ };
+ }
+ default: {
+ return {
+ ...state,
+ };
+ }
+ }
+}
diff --git a/00 Base/src/redux/store.js b/00 Base/src/redux/store.js
new file mode 100644
index 0000000..e7c2c33
--- /dev/null
+++ b/00 Base/src/redux/store.js
@@ -0,0 +1,12 @@
+import { createStore, applyMiddleware, compose } from 'redux';
+import thunk from 'redux-thunk';
+import rootReducer from './reducers/index';
+
+const initialState = {};
+const middleware = [thunk];
+const store=createStore(rootReducer,initialState,compose(
+ applyMiddleware(thunk),
+ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
+
+))
+export default store;
diff --git a/00 Base/src/styles.less b/00 Base/src/styles.less
new file mode 100644
index 0000000..af8d565
--- /dev/null
+++ b/00 Base/src/styles.less
@@ -0,0 +1,3 @@
+body {
+ background-color: #afafdc;
+}
diff --git a/00 Base/tsconfig.json b/00 Base/tsconfig.json
index 8a26838..3e07872 100644
--- a/00 Base/tsconfig.json
+++ b/00 Base/tsconfig.json
@@ -11,7 +11,9 @@
"allowJs": true,
"suppressImplicitAnyIndexErrors": true,
"skipLibCheck": true,
- "esModuleInterop": true
+ "esModuleInterop": true,
+ "outDir": "generated",
},
- "include": ["./src/**/*"]
+ "include": ["./src/**/*"],
+ "exclude": ["node_modules", "**/*.test.ts", "dist"]
}