Compare commits

4 Commits

33 changed files with 13586 additions and 45 deletions

View File

@@ -1,5 +1,30 @@
# subtaskio # subtaskio
Subtask Io Subtask Io
frontend - React JS frontend #Frontend
## How to use - React JS frontend
- Clone git
- Switch to frontend directory
`$ cd frontend`
- Install dependencies:
`$ npm install`
- Host dev environment and start
`$ npm start`
## Stack:
- React 16.3.1
- React-Router-dom 4.2.2
- babel-core 6.26.0
- Webpack 3.8.1
- Unit Testing :
- Jest 22.4.3.8
- Enzyme 3.3.0
#Backend
backend - Aws Lambda backend backend - Aws Lambda backend

View File

@@ -79,6 +79,7 @@ module.exports = function(proxy, allowedHost) {
// See https://github.com/facebookincubator/create-react-app/issues/387. // See https://github.com/facebookincubator/create-react-app/issues/387.
disableDotRule: true, disableDotRule: true,
}, },
historyApiFallback: true,
public: allowedHost, public: allowedHost,
proxy, proxy,
before(app) { before(app) {

12963
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -40,7 +40,10 @@
"react": "^16.3.1", "react": "^16.3.1",
"react-dev-utils": "^5.0.1", "react-dev-utils": "^5.0.1",
"react-dom": "^16.3.1", "react-dom": "^16.3.1",
"react-redux": "^5.0.7",
"react-router-dom": "^4.2.2",
"react-test-renderer": "^16.3.1", "react-test-renderer": "^16.3.1",
"redux": "^3.7.2",
"resolve": "1.6.0", "resolve": "1.6.0",
"sass-loader": "^6.0.7", "sass-loader": "^6.0.7",
"style-loader": "0.19.0", "style-loader": "0.19.0",
@@ -59,6 +62,7 @@
"watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive" "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive"
}, },
"jest": { "jest": {
"setupTestFrameworkScriptFile": "<rootDir>/src/setupTests.js",
"collectCoverageFrom": [ "collectCoverageFrom": [
"src/**/*.{js,jsx,mjs}" "src/**/*.{js,jsx,mjs}"
], ],

View File

@@ -19,7 +19,7 @@
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>React App</title> <title>Sutask io</title>
</head> </head>
<body> <body>
<noscript> <noscript>

View File

@@ -1,6 +1,6 @@
{ {
"short_name": "React App", "short_name": "Subtask IO",
"name": "Create React App Sample", "name": "Subtask",
"icons": [ "icons": [
{ {
"src": "favicon.ico", "src": "favicon.ico",

View File

@@ -0,0 +1,6 @@
export const INIT_PAGE = 'INIT_PAGE'
export const initPage = titles => ({
type: INIT_PAGE,
titles
})

View File

@@ -1,9 +1,10 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import {MemoryRouter as Router} from 'react-router-dom'
import App from './index'; import App from './index';
it('renders without crashing', () => { it('renders without crashing', () => {
const div = document.createElement('div'); const div = document.createElement('div');
ReactDOM.render(<App />, div); ReactDOM.render(<Router><App/></Router>, div);
ReactDOM.unmountComponentAtNode(div); ReactDOM.unmountComponentAtNode(div);
}); });

View File

@@ -0,0 +1,111 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Daoardshb renders correctly 1`] = `
ShallowWrapper {
"length": 1,
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <HashRouter>
<Unknown
title="Dashboard"
/>
</HashRouter>,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
"getNode": [Function],
"render": [Function],
"simulateEvent": [Function],
"unmount": [Function],
},
Symbol(enzyme.__node__): Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": <Unknown
title="Dashboard"
/>,
"history": Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {
"title": "Dashboard",
},
"ref": null,
"rendered": null,
"type": [Function],
},
"type": [Function],
},
Symbol(enzyme.__nodes__): Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": <Unknown
title="Dashboard"
/>,
"history": Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {
"title": "Dashboard",
},
"ref": null,
"rendered": null,
"type": [Function],
},
"type": [Function],
},
],
Symbol(enzyme.__options__): Object {
"adapter": ReactSixteenAdapter {
"options": Object {
"enableComponentDidUpdateOnSetState": true,
},
},
},
}
`;

View File

@@ -0,0 +1,13 @@
import React from 'react';
import {HashRouter as Router} from 'react-router-dom'
import {Dashboard} from './index';
import {shallow} from 'enzyme'
import renderer from 'react-test-renderer';
test('Daoardshb renders correctly', () => {
const props = {
title: 'Dashboard'
}
const component = shallow((<Router><Dashboard {...props} /></Router>))
expect(component).toMatchSnapshot();
});

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { connect } from 'react-redux'
import styles from './styles.scss';
import Header from '../Header/index'
export const Dashboard = (props) => {
return <div className={styles.colored}><Header />Title {props.title}</div>;
};
const mapStateToProps = (state) => {
return {
title: state.pageInitialised.titles.title,
}
}
export default connect(mapStateToProps)(Dashboard);

View File

@@ -1,10 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Dummy renders correctly 1`] = `
<div
className={undefined}
>
Hello,
Hamo
</div>
`;

View File

@@ -1,8 +0,0 @@
import React from 'react';
import styles from './styles.scss';
const Dummy = (props) => {
return <div className={styles.colored}>Hello, {props.name}</div>;
};
export default Dummy;

View File

@@ -1,3 +0,0 @@
.colored {
color: blueviolet;
}

View File

@@ -0,0 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Header renders correctly 1`] = `
<div
className={undefined}
>
<h1>
SUBTASK
</h1>
<div
className={undefined}
>
<a
className={undefined}
href="#/mytask"
onClick={[Function]}
>
My Task
</a>
<a
className={undefined}
href="#/inbox"
onClick={[Function]}
>
Inbox
</a>
<a
className={undefined}
href="#/dashboard"
onClick={[Function]}
>
Dashboard
</a>
</div>
</div>
`;

View File

@@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import Dummy from './index'; import {HashRouter as Router} from 'react-router-dom'
import Header from './index';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
test('Dummy renders correctly', () => { test('Header renders correctly', () => {
const component = renderer.create( const component = renderer.create(
<Dummy name="Hamo"/>, <Router><Header/></Router>,
); );
let tree = component.toJSON(); let tree = component.toJSON();
expect(tree).toMatchSnapshot(); expect(tree).toMatchSnapshot();
}); });

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { Link } from 'react-router-dom'
import styles from './styles.scss';
export const Header = () => {
return <div className={styles.Header}>
<h1>SUBTASK</h1>
<div className={styles.HeaderContainer}>
<Link className={styles.HeaderContainer} to="/mytask">My Task</Link>
<Link className={styles.HeaderContainer} to="/inbox">Inbox</Link>
<Link className={styles.HeaderContainer} to="/dashboard">Dashboard</Link>
</div>
</div>;
};
export default Header;

View File

@@ -0,0 +1,14 @@
.Header {
padding-top : 6px;
padding-bottom : 6px;
display: flex;
justify-content: space-between;
align-items:center;
display: flex;
margin-left: 8px;
margin-right: 8px;
}
.HeaderContainer {
padding: 10px;
}

View File

@@ -0,0 +1,111 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Inbox renders correctly 1`] = `
ShallowWrapper {
"length": 1,
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <HashRouter>
<Unknown
title="Inbox"
/>
</HashRouter>,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
"getNode": [Function],
"render": [Function],
"simulateEvent": [Function],
"unmount": [Function],
},
Symbol(enzyme.__node__): Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": <Unknown
title="Inbox"
/>,
"history": Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {
"title": "Inbox",
},
"ref": null,
"rendered": null,
"type": [Function],
},
"type": [Function],
},
Symbol(enzyme.__nodes__): Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": <Unknown
title="Inbox"
/>,
"history": Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {
"title": "Inbox",
},
"ref": null,
"rendered": null,
"type": [Function],
},
"type": [Function],
},
],
Symbol(enzyme.__options__): Object {
"adapter": ReactSixteenAdapter {
"options": Object {
"enableComponentDidUpdateOnSetState": true,
},
},
},
}
`;

View File

@@ -0,0 +1,13 @@
import React from 'react';
import {HashRouter as Router} from 'react-router-dom'
import {shallow} from 'enzyme'
import {Inbox} from './index';
import renderer from 'react-test-renderer';
test('Inbox renders correctly', () => {
const props = {
title: 'Inbox'
}
const component = shallow((<Router><Inbox {...props} /></Router>))
expect(component).toMatchSnapshot();
});

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { connect } from 'react-redux'
import styles from './styles.scss';
import Header from '../Header/index'
export const Inbox = (props) => {
return <div className={styles.colored}><Header />Title {props.title}</div>;
};
const mapStateToProps = (state) => {
return {
title: state.pageInitialised.titles.title,
}
}
export default connect(mapStateToProps)(Inbox);

View File

@@ -0,0 +1,111 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MyTask renders correctly 1`] = `
ShallowWrapper {
"length": 1,
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <HashRouter>
<Unknown
title="MyTask"
/>
</HashRouter>,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
"getNode": [Function],
"render": [Function],
"simulateEvent": [Function],
"unmount": [Function],
},
Symbol(enzyme.__node__): Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": <Unknown
title="MyTask"
/>,
"history": Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {
"title": "MyTask",
},
"ref": null,
"rendered": null,
"type": [Function],
},
"type": [Function],
},
Symbol(enzyme.__nodes__): Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": <Unknown
title="MyTask"
/>,
"history": Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {
"title": "MyTask",
},
"ref": null,
"rendered": null,
"type": [Function],
},
"type": [Function],
},
],
Symbol(enzyme.__options__): Object {
"adapter": ReactSixteenAdapter {
"options": Object {
"enableComponentDidUpdateOnSetState": true,
},
},
},
}
`;

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { connect } from 'react-redux'
import styles from './styles.scss';
import Header from '../Header/index'
export const MyTask = (props) => {
return <div className={styles.colored}><Header />Title {props.title}</div>;
};
const mapStateToProps = (state) => {
return {
title: state.pageInitialised.titles.title,
}
}
export default connect(mapStateToProps)(MyTask);

View File

@@ -0,0 +1,14 @@
import React from 'react';
import {HashRouter as Router} from 'react-router-dom'
import {shallow} from 'enzyme'
import {MyTask} from './index';
import renderer from 'react-test-renderer';
test('MyTask renders correctly', () => {
const props = {
title: 'MyTask'
}
const component = shallow((<Router><MyTask {...props} /></Router>))
expect(component).toMatchSnapshot();
});

View File

@@ -0,0 +1,53 @@
import React, { Component } from 'react'
import { Provider } from 'react-redux'
import { BrowserRouter as Router, Route } from 'react-router-dom'
import App from '../index'
import Inbox from '../Inbox/index';
import MyTask from '../Mytask/index';
import Dashboard from '../Dashboard/index';
import { initPage } from '../../../actions/index'
class Root extends Component {
render() {
return (
<Provider store={this.props.store}>
<Router>
<div>
<Route path='/' exact component={App} />
<Route path="/mytask" exact component={mountComponentWithPathAction(MyTask, this.props)} />
<Route path="/inbox" exact component={mountComponentWithPathAction(Inbox, this.props)} />
<Route path="/dashboard" exact component={mountComponentWithPathAction(Dashboard, this.props)} />
</div>
</Router>
</Provider>
);
}
}
/**
* Pass this function to `react-router-dom` Route component property
* In orderd to triger redux action that is related to te Route path
* If such action exists
*
* @param {} WrappedComponent a React component to render
* @param {*} props a an object containing redux `store` to dispach initPage action with route path name
* @returns A new HIGHT ORDER React component from WrappedComponent after redux action is dispatched
*/
let mountComponentWithPathAction = function (WrappedComponent, props) {
return class extends Component {
componentWillMount() {
props.store.dispatch(initPage({ pathName: this.props.location.pathname }))
}
render() {
return <WrappedComponent {...this.props} />
}
}
}
export default Root

View File

@@ -1,20 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import logo from '../../logo.svg'; import Header from './Header';
import styles from './styles.scss';
import Dummy from './Dummy';
class App extends Component { class App extends Component {
render() { render() {
return ( return (
<div className={styles.App}> <Header />
<header className={styles["App-header"]}>
<img src={logo} className={styles["App-logo"]} alt="logo" />
<h1 className={styles["App-title"]}><Dummy name="Hamo"/> </h1>
</header>
<p className={styles["App-intro"]}>
To get started ba, edit <code>src/App.js</code> and save to reload.
</p>
</div>
); );
} }
} }

View File

@@ -1,8 +1,15 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { createStore } from 'redux'
import './index.css'; import './index.css';
import App from './components/App';
import registerServiceWorker from './registerServiceWorker'; import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root')); import subtaskIo from './reducers/index'
import Root from './components/App/Root/index';
const store = createStore(subtaskIo)
ReactDOM.render(
<Root store={store} />,
document.getElementById('root'));
registerServiceWorker(); registerServiceWorker();

View File

@@ -0,0 +1,7 @@
import { combineReducers } from 'redux'
import pageInitialised from './router'
const subtaskIo = combineReducers({
pageInitialised,
})
export default subtaskIo

View File

@@ -0,0 +1,14 @@
const pageInitialised = (state = {
titles: { title: 'Default title' },
}, action) => {
switch (action.type) {
case 'INIT_PAGE':
return {
...state,
titles: { title: action.titles.pathName }
}
default:
return state
}
}
export default pageInitialised

View File

@@ -0,0 +1,4 @@
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });