diff --git a/.gitignore b/.gitignore
index 598afce..b6f8f7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
node_modules
src/dist/public
-src/server/logs
\ No newline at end of file
+src/server/logs
+logs
\ No newline at end of file
diff --git a/config/library.js b/config/library.js
index 613e265..464e0ba 100644
--- a/config/library.js
+++ b/config/library.js
@@ -2,5 +2,5 @@ require('react');
require('react-dom');
require('react-redux');
require('redux');
-require('react-player');
-require('redux-saga');
\ No newline at end of file
+require('redux-saga');
+require('react-bootstrap');
diff --git a/package.json b/package.json
index 4998dd0..b73b77b 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,8 @@
"babel-eslint": "^8.2.6",
"babel-loader": "^8.0.0-beta.4",
"babel-polyfill": "^6.26.0",
+ "chai": "^4.1.2",
+ "chai-enzyme": "^1.0.0-beta.1",
"chromedriver": "^2.41.0",
"css-loader": "^1.0.0",
"cucumber": "^4.2.1",
@@ -43,6 +45,7 @@
"express": "^4.16.3",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"jsdom": "^12.0.0",
+ "jsdom-global": "^3.0.2",
"mocha": "^5.2.0",
"nightwatch": "^0.9.21",
"nightwatch-cucumber": "^9.1.2",
@@ -54,17 +57,18 @@
"style-loader": "^0.22.1",
"stylelint": "^9.5.0",
"stylelint-config-standard": "^18.2.0",
+ "testdouble": "^3.8.1",
"uglifyjs-webpack-plugin": "^1.3.0",
"webpack": "^4.17.0",
"webpack-cli": "^3.1.0"
},
"dependencies": {
- "react-player": "^1.6.4",
- "redux-saga": "^0.16.0",
"react": "^16.4.2",
+ "react-bootstrap": "^0.32.3",
"react-dom": "^16.4.2",
"react-redux": "^5.0.7",
- "redux": "^4.0.0"
+ "redux": "^4.0.0",
+ "redux-saga": "^0.16.0"
},
"nyc": {
"extension": [
diff --git a/src/client/actions/index.js b/src/client/actions/index.js
index b89ccf6..e5b0622 100644
--- a/src/client/actions/index.js
+++ b/src/client/actions/index.js
@@ -1,4 +1,8 @@
import { action } from '../helpers/actionCreator';
import * as actions from '../constants/index';
-export const add = text => action(actions.ADD, { text });
+export const add = number => action(actions.ADD, { number });
+
+export const sub = number => action(actions.SUB, { number });
+
+export const change = number => action(actions.CHANGE, { number });
diff --git a/src/client/components/App.jsx b/src/client/components/App.jsx
index 941e7e1..24dde61 100644
--- a/src/client/components/App.jsx
+++ b/src/client/components/App.jsx
@@ -1,35 +1,37 @@
import React from 'react';
import PropTypes from 'prop-types';
-import ReactPlayer from 'react-player';
+
import '../styles/App.scss';
+import { Button, FormControl } from 'react-bootstrap';
export default class App extends React.Component {
constructor(props) {
super(props);
+ this.handleChange = this.handleChange.bind(this);
+ }
+ handleChange(e) {
+ parseInt(e.target.value) ? this.props.change(parseInt(e.target.value)): '';
}
render() {
- return this.props.number == 1 ? (
-
-
-
- ) : (
-
- {' '}
- Welcome{' '}
- this.props.add(1)}
- />{' '}
-
+ return (
+
+
+
+
);
}
}
App.propTypes = {
- number: PropTypes.string,
+ number: PropTypes.number,
add: PropTypes.func,
- recipes: PropTypes.array,
+ sub: PropTypes.func,
+ change: PropTypes.func,
};
diff --git a/src/client/constants/index.js b/src/client/constants/index.js
index d319394..ac819ba 100644
--- a/src/client/constants/index.js
+++ b/src/client/constants/index.js
@@ -1,4 +1,5 @@
+export const INITALIZE_APPLICATION = 'INITALIZE_APPLICATION';
+export const CHANGE = 'CHANGE';
export const ADD = 'ADD';
+export const SUB = 'SUB';
export const SAVE = 'SAVE';
-export const START_APPLICATION = 'START_APPLICATION';
-export const ADD_RECIPES = 'ADD_RECIPES';
diff --git a/src/client/containers/App.js b/src/client/containers/App.js
index dac1c0e..aaf0ee6 100644
--- a/src/client/containers/App.js
+++ b/src/client/containers/App.js
@@ -1,17 +1,18 @@
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import App from '../components/App';
-import { add } from '../actions';
+import { add, sub, change } from '../actions';
const mapStateToProps = state => ({
- number: state.add.number,
- recipes: state.recipe.recipes,
+ number: state.calc.number,
});
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
add,
+ sub,
+ change,
},
dispatch
);
diff --git a/src/client/index.js b/src/client/index.js
index 02be6ce..dc5da95 100644
--- a/src/client/index.js
+++ b/src/client/index.js
@@ -1,18 +1,19 @@
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
-import { createStore, applyMiddleware } from 'redux';
+import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
+const sagaMiddleware = createSagaMiddleware();
import cr from './reducers/combined';
import AppContainer from './containers/App';
import rootSaga from './sagas/combined';
-const sagaMiddleware = createSagaMiddleware();
+const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
import * as actions from './constants';
// then run the saga
-const store = createStore(cr, applyMiddleware(sagaMiddleware));
+const store = createStore(cr, composeEnhancers(applyMiddleware(sagaMiddleware)));
sagaMiddleware.run(rootSaga);
export const action = type => store.dispatch({ type });
-action(actions.START_APPLICATION);
+action(actions.INITALIZE_APPLICATION);
render(
diff --git a/src/client/reducers/add.js b/src/client/reducers/calc.js
similarity index 76%
rename from src/client/reducers/add.js
rename to src/client/reducers/calc.js
index ab38ca9..c74b879 100644
--- a/src/client/reducers/add.js
+++ b/src/client/reducers/calc.js
@@ -1,6 +1,6 @@
import * as actions from '../constants';
-const add = (state = {}, action) => {
+const calc = (state = {}, action) => {
switch (action.type) {
case actions.SAVE:
return Object.assign({}, state, {
@@ -11,4 +11,4 @@ const add = (state = {}, action) => {
}
};
-export default add;
+export default calc;
diff --git a/src/client/reducers/combined.js b/src/client/reducers/combined.js
index 9e41f0f..e2f8b61 100644
--- a/src/client/reducers/combined.js
+++ b/src/client/reducers/combined.js
@@ -1,10 +1,8 @@
import { combineReducers } from 'redux';
-import add from './add';
-import recipe from './recipe';
+import calc from './calc';
const combined = combineReducers({
- add,
- recipe,
+ calc,
});
export default combined;
diff --git a/src/client/reducers/recipe.js b/src/client/reducers/recipe.js
deleted file mode 100644
index 0b04ee1..0000000
--- a/src/client/reducers/recipe.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import * as actions from '../constants';
-
-const recipe = (state = {}, action) => {
- switch (action.type) {
- case actions.ADD_RECIPES:
- return Object.assign({}, state, {
- recipes: action.recipes,
- });
- default:
- return state;
- }
-};
-
-export default recipe;
diff --git a/src/client/sagas/add.js b/src/client/sagas/add.js
deleted file mode 100644
index 2be08e1..0000000
--- a/src/client/sagas/add.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { call, put, takeEvery, takeLatest } from 'redux-saga/effects';
-import * as actions from '../constants';
-import { action } from '../helpers/actionCreator';
-
-function* addnumber(action) {
- try {
- yield put({ type: actions.SAVE, number: '1' });
- } catch (e) {
- yield put({ type: 'ADD_FAILED', message: e.message });
- }
-}
-
-function* addWatcher() {
- yield takeEvery(actions.ADD, addnumber);
-}
-export default addWatcher;
diff --git a/src/client/sagas/calc.js b/src/client/sagas/calc.js
new file mode 100644
index 0000000..b51ad1f
--- /dev/null
+++ b/src/client/sagas/calc.js
@@ -0,0 +1,56 @@
+import { put, takeEvery } from 'redux-saga/effects';
+import * as actions from '../constants';
+
+function* initalize() {
+ try {
+ yield put({
+ type: actions.SAVE,
+ number: 100,
+ });
+ } catch (e) {
+ yield put({ type: 'INITALIZE_APPLICATION_FAILED', number: null });
+ }
+}
+
+export function* initalizeWatcher() {
+ yield takeEvery(actions.INITALIZE_APPLICATION, initalize);
+}
+
+function* addnumber(action) {
+ try {
+ yield put({ type: actions.SAVE, number: action.payload.number + 1 });
+ } catch (e) {
+ yield put({ type: 'ADD_FAILED', message: e.message });
+ }
+}
+
+export function* addWatcher() {
+ yield takeEvery(actions.ADD, addnumber);
+}
+export default addWatcher;
+
+
+function* changenumber(action) {
+ try {
+ yield put({ type: actions.SAVE, number: action.payload.number });
+ } catch (e) {
+ yield put({ type: 'CHANGE_FAILED', message: e.message });
+ }
+}
+
+export function* changeWatcher() {
+ yield takeEvery(actions.CHANGE, changenumber);
+}
+
+
+function* subnumber(action) {
+ try {
+ yield put({ type: actions.SAVE, number: action.payload.number - 1 });
+ } catch (e) {
+ yield put({ type: 'SUB_FAILED', message: e.message });
+ }
+}
+
+export function* subWatcher() {
+ yield takeEvery(actions.SUB, subnumber);
+}
\ No newline at end of file
diff --git a/src/client/sagas/combined.js b/src/client/sagas/combined.js
index 1e7a30d..de8e82f 100644
--- a/src/client/sagas/combined.js
+++ b/src/client/sagas/combined.js
@@ -1,6 +1,5 @@
import { all } from 'redux-saga/effects';
-import addWatcher from './add';
-import startWatcher from './start';
+import * as watcher from './calc';
export default function* rootSaga() {
- yield all([addWatcher(), startWatcher()]);
+ yield all([watcher.addWatcher(), watcher.subWatcher(), watcher.changeWatcher(), watcher.initalizeWatcher()]);
}
diff --git a/src/client/sagas/start.js b/src/client/sagas/start.js
deleted file mode 100644
index 279c396..0000000
--- a/src/client/sagas/start.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { call, put, takeEvery, takeLatest } from 'redux-saga/effects';
-import * as actions from '../constants';
-import { callFetch } from '../services/api';
-function* initalize() {
- try {
- const recipes = yield call(callFetch, '/recipes');
- yield put({
- type: actions.ADD_RECIPES,
- recipes: JSON.parse(recipes.response),
- });
- } catch (e) {
- yield put({ type: 'START_APPLICATION_FAILED', recipes: [] });
- }
-}
-
-function* startWatcher() {
- yield takeEvery(actions.START_APPLICATION, initalize);
-}
-export default startWatcher;
diff --git a/src/client/styles/App.scss b/src/client/styles/App.scss
index 1247c62..df15a71 100644
--- a/src/client/styles/App.scss
+++ b/src/client/styles/App.scss
@@ -4,4 +4,13 @@
background-color: #fff;
text-transform: capitalize;
text-align: left;
+ .btn {
+ margin: 10px;
+ }
+ .form-control {
+ margin: 10px;
+ display: unset;
+ width: unset;
+ padding-bottom: 9px;
+ }
}
diff --git a/src/dist/index.html b/src/dist/index.html
index dc89d6b..6fcb732 100644
--- a/src/dist/index.html
+++ b/src/dist/index.html
@@ -10,6 +10,7 @@
+
diff --git a/test/mocha.opts b/test/mocha.opts
index 213b8e7..33bc6bc 100644
--- a/test/mocha.opts
+++ b/test/mocha.opts
@@ -4,5 +4,5 @@
--require @babel/register
--require babel-polyfill
--require test/mochasetup.js
---require jsdom
+--require jsdom-global/register
test/unit/**/*.{js,jsx}
\ No newline at end of file
diff --git a/test/unit/client/actions/indexTest.js b/test/unit/client/actions/indexTest.js
new file mode 100644
index 0000000..c8cbd92
--- /dev/null
+++ b/test/unit/client/actions/indexTest.js
@@ -0,0 +1,28 @@
+import { describe, it } from 'mocha';
+import { expect } from 'chai';
+import * as action from '../../../../src/client/actions';
+import * as actions from '../../../../src/client/constants';
+
+describe('actions', () => {
+ it('should trigger the ADD action', () => {
+ const number = 100;
+ const response = action.add(number);
+
+ expect(response.type).to.deep.equal(actions.ADD);
+ expect(response.payload.number).to.deep.equal(100);
+ });
+ it('should trigger the SUB action', () => {
+ const number = 100;
+ const response = action.sub(number);
+
+ expect(response.type).to.deep.equal(actions.SUB);
+ expect(response.payload.number).to.deep.equal(100);
+ });
+ it('should trigger the CHANGE action', () => {
+ const number = 100;
+ const response = action.change(number);
+
+ expect(response.type).to.deep.equal(actions.CHANGE);
+ expect(response.payload.number).to.deep.equal(100);
+ });
+});
diff --git a/test/unit/client/components/AppTest.js b/test/unit/client/components/AppTest.js
index dda0373..9613bf4 100644
--- a/test/unit/client/components/AppTest.js
+++ b/test/unit/client/components/AppTest.js
@@ -1,17 +1,39 @@
+import { describe, it, beforeEach } from 'mocha';
import React from 'react';
-import expect from 'expect';
-import Enzyme from 'enzyme';
+import Enzyme, { mount, shallow } from 'enzyme';
+import td from 'testdouble';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
-import { shallow, mount } from 'enzyme';
+import chai, { expect } from 'chai';
+import chaiEnzyme from 'chai-enzyme';
+chai.use(chaiEnzyme());
import App from '../../../../src/client/components/App';
-let wrapper = shallow();
-describe('App Component', () => {
+
+describe.only('App Component', () => {
+ let wrapper, addspy, subspy, changespy;
+ beforeEach(function () {
+ addspy = td.function();
+ subspy = td.function();
+ changespy = td.function();
+ wrapper = mount();
+ });
it('renders div', () => {
- expect(wrapper.find('div').text()).toContain('Welcome');
+ expect(wrapper.find('div').exists()).to.be.true;
+ expect(wrapper.props().number).to.equal(100);
+ });
+ it('should render two button', () => {
+ expect(wrapper.find('button').hostNodes()).to.have.lengthOf(2);
+ });
+ it('verify add click', () => {
+ wrapper.find('button').hostNodes().at(0).simulate('click');
+ td.verify(addspy(100));
+ });
+ it('verify sub click', () => {
+ wrapper.find('button').hostNodes().at(1).simulate('click');
+ td.verify(subspy(100));
});
- it('renders react player div', () => {
- wrapper = shallow();
- expect(wrapper.find('div').text()).toContain('');
+ it('should call text box value change event', () => {
+ wrapper.instance().handleChange({ 'target': { 'value': 300 } });
+ td.verify(changespy(300));
});
});
diff --git a/test/unit/client/containers/AppTest.js b/test/unit/client/containers/AppTest.js
new file mode 100644
index 0000000..e69de29