diff --git a/package.json b/package.json index 7d6e5b868..bcf6df7d7 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "html-webpack-plugin": "2.22.0", "isparta": "4.0.0", "mocha": "3.0.1", + "mockdate": "1.0.4", "node-sass": "3.8.0", "npm-run-all": "2.3.0", "open": "0.0.5", diff --git a/src/actions/fuelSavingsActions.js b/src/actions/fuelSavingsActions.js index 627db9179..66c7e1cfe 100644 --- a/src/actions/fuelSavingsActions.js +++ b/src/actions/fuelSavingsActions.js @@ -1,14 +1,26 @@ import * as types from '../constants/actionTypes'; +import dateHelper from '../utils/dateHelper'; + // example of a thunk using the redux-thunk middleware export function saveFuelSavings(settings) { return function (dispatch) { // thunks allow for pre-processing actions, calling apis, and dispatching multiple actions // in this case at this point we could call a service that would persist the fuel savings - return dispatch({type: types.SAVE_FUEL_SAVINGS, settings}); + return dispatch({ + type: types.SAVE_FUEL_SAVINGS, + dateModified: dateHelper.getFormattedDateTime(), + settings + }); }; } export function calculateFuelSavings(settings, fieldName, value) { - return {type: types.CALCULATE_FUEL_SAVINGS, settings, fieldName, value}; + return { + type: types.CALCULATE_FUEL_SAVINGS, + dateModified: dateHelper.getFormattedDateTime(), + settings, + fieldName, + value + }; } diff --git a/src/actions/fuelSavingsActions.spec.js b/src/actions/fuelSavingsActions.spec.js index 165c39a83..914c658f4 100644 --- a/src/actions/fuelSavingsActions.spec.js +++ b/src/actions/fuelSavingsActions.spec.js @@ -1,12 +1,23 @@ -import chai, { expect } from 'chai'; +import * as ActionTypes from '../constants/actionTypes'; +import * as ActionCreators from './fuelSavingsActions'; + import sinon from 'sinon'; import sinonChai from 'sinon-chai'; -import * as ActionCreators from './fuelSavingsActions'; -import * as ActionTypes from '../constants/actionTypes'; +import MockDate from 'mockdate'; +import chai, { expect } from 'chai'; + +import dateHelper from '../utils/dateHelper'; chai.use(sinonChai); describe('Actions', () => { + let dateModified; + before(() => { + MockDate.set(new Date()); + dateModified = dateHelper.getFormattedDateTime(); + }); + after(() => MockDate.reset()); + const appState = { newMpg: 20, tradeMpg: 10, @@ -28,6 +39,7 @@ describe('Actions', () => { const dispatch = sinon.spy(); const expected = { type: ActionTypes.SAVE_FUEL_SAVINGS, + dateModified, settings: appState }; @@ -42,15 +54,16 @@ describe('Actions', () => { it('should create an action to calculate fuel savings', () => { const fieldName = 'newMpg'; const value = 100; - + const actual = ActionCreators.calculateFuelSavings(appState, fieldName, value); const expected = { type: ActionTypes.CALCULATE_FUEL_SAVINGS, + dateModified, settings: appState, fieldName, value }; - expect(ActionCreators.calculateFuelSavings(appState, fieldName, value)).to.deep.equal(expected); // Notice use of deep because it's a nested object - // expect(ActionCreators.calculateFuelSavings(appState, fieldName, value)).to.equal(expected); // Fails. Not deeply equal + expect(actual).to.deep.equal(expected); // Notice use of deep because it's a nested object + // expect(actual).to.equal(expected); // Fails. Not deeply equal }); }); diff --git a/src/reducers/fuelSavingsReducer.js b/src/reducers/fuelSavingsReducer.js index b466cd940..44ae621bd 100644 --- a/src/reducers/fuelSavingsReducer.js +++ b/src/reducers/fuelSavingsReducer.js @@ -1,6 +1,5 @@ import {SAVE_FUEL_SAVINGS, CALCULATE_FUEL_SAVINGS} from '../constants/actionTypes'; import calculator from '../utils/fuelSavingsCalculator'; -import dateHelper from '../utils/dateHelper'; import objectAssign from 'object-assign'; import initialState from './initialState'; @@ -16,13 +15,13 @@ export default function fuelSavingsReducer(state = initialState.fuelSavings, act case SAVE_FUEL_SAVINGS: // For this example, just simulating a save by changing date modified. // In a real app using Redux, you might use redux-thunk and handle the async call in fuelSavingsActions.js - return objectAssign({}, state, {dateModified: dateHelper.getFormattedDateTime(new Date())}); + return objectAssign({}, state, {dateModified: action.dateModified}); case CALCULATE_FUEL_SAVINGS: newState = objectAssign({}, state); newState[action.fieldName] = action.value; newState.necessaryDataIsProvidedToCalculateSavings = calculator().necessaryDataIsProvidedToCalculateSavings(newState); - newState.dateModified = dateHelper.getFormattedDateTime(new Date()); + newState.dateModified = action.dateModified; if (newState.necessaryDataIsProvidedToCalculateSavings) { newState.savings = calculator().calculateSavings(newState); diff --git a/src/reducers/fuelSavingsReducer.spec.js b/src/reducers/fuelSavingsReducer.spec.js index 3129662a7..2dd926f1b 100644 --- a/src/reducers/fuelSavingsReducer.spec.js +++ b/src/reducers/fuelSavingsReducer.spec.js @@ -41,6 +41,7 @@ describe('Reducers::FuelSavings', () => { } }; }; + const dateModified = dateHelper.getFormattedDateTime(new Date()); it('should set initial state by default', () => { const action = { type: 'unknown' }; @@ -51,14 +52,14 @@ describe('Reducers::FuelSavings', () => { }); it('should handle SAVE_FUEL_SAVINGS', () => { - const action = { type: ActionTypes.SAVE_FUEL_SAVINGS, settings: getAppState() }; - const expected = Object.assign(getAppState(), {dateModified: dateHelper.getFormattedDateTime(new Date())}); + const action = { type: ActionTypes.SAVE_FUEL_SAVINGS, dateModified, settings: getAppState() }; + const expected = Object.assign(getAppState(), { dateModified }); expect(reducer(getAppState(), action)).to.deep.equal(expected); }); it('should handle CALCULATE_FUEL_SAVINGS', () => { - const action = { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: getAppState(), fieldName: 'newMpg', value: 30 }; + const action = { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: getAppState(), fieldName: 'newMpg', value: 30 }; const expectedMpg = 30; const expectedSavings = { monthly: '$43.33', annual: '$519.96', threeYear: '$1,559.88' }; diff --git a/src/store/store.spec.js b/src/store/store.spec.js index 9cecc0dbd..9abb00ef0 100644 --- a/src/store/store.spec.js +++ b/src/store/store.spec.js @@ -1,22 +1,32 @@ import * as ActionTypes from '../constants/actionTypes'; -import { createStore } from 'redux'; + +import MockDate from 'mockdate'; import { expect } from 'chai'; -import rootReducer from '../reducers'; +import { createStore } from 'redux'; + import calculator from '../utils/fuelSavingsCalculator'; import dateHelper from '../utils/dateHelper'; import initialState from '../reducers/initialState'; +import rootReducer from '../reducers'; describe('Store', () => { + let dateModified; + before(() => { + MockDate.set(new Date()); + dateModified = dateHelper.getFormattedDateTime(); + }); + after(() => MockDate.reset()); + it('should display results when necessary data is provided', () => { const store = createStore(rootReducer, initialState); const actions = [ - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'newMpg', value: 20 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'tradeMpg', value: 10 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'newPpg', value: 1.50 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'tradePpg', value: 1.50 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'milesDriven', value: 100 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'milesDrivenTimeframe', value: 'month' } + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'newMpg', value: 20 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'tradeMpg', value: 10 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'newPpg', value: 1.50 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'tradePpg', value: 1.50 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'milesDriven', value: 100 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'milesDrivenTimeframe', value: 'month' } ]; actions.forEach(action => store.dispatch(action)); @@ -29,7 +39,7 @@ describe('Store', () => { milesDriven: 100, milesDrivenTimeframe: 'month', displayResults: false, - dateModified: dateHelper.getFormattedDateTime(new Date()), + dateModified, necessaryDataIsProvidedToCalculateSavings: true, savings: calculator().calculateSavings(store.getState().fuelSavings) }; @@ -41,12 +51,12 @@ describe('Store', () => { const store = createStore(rootReducer, initialState); const actions = [ - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'newMpg', value: 20 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'tradeMpg', value: 10 }, - // { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'newPpg', value: 1.50 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'tradePpg', value: 1.50 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'milesDriven', value: 100 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'milesDrivenTimeframe', value: 'month' } + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'newMpg', value: 20 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'tradeMpg', value: 10 }, + // { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'newPpg', value: 1.50 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'tradePpg', value: 1.50 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'milesDriven', value: 100 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'milesDrivenTimeframe', value: 'month' } ]; actions.forEach(action => store.dispatch(action)); @@ -61,7 +71,7 @@ describe('Store', () => { milesDriven: 100, milesDrivenTimeframe: 'month', displayResults: false, - dateModified: dateHelper.getFormattedDateTime(new Date()), + dateModified, necessaryDataIsProvidedToCalculateSavings: false, savings: { annual: 0, monthly: 0, threeYear: 0 } }; @@ -75,31 +85,31 @@ describe('Store', () => { const store = createStore(rootReducer, initialState); const actions = [ - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'newMpg', value: 20 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'tradeMpg', value: 10 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'newPpg', value: 1.50 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'tradePpg', value: 1.50 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'milesDriven', value: 100 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'milesDrivenTimeframe', value: 'month' }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'newMpg', value: 20 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'tradeMpg', value: 10 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'newPpg', value: 1.50 }, - { type: ActionTypes.SAVE_FUEL_SAVINGS, settings: store.getState() }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'tradePpg', value: 1.50 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'milesDriven', value: 100 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'milesDrivenTimeframe', value: 'week' }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'newMpg', value: 20 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'tradeMpg', value: 10 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'newPpg', value: 1.50 } + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'newMpg', value: 20 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'tradeMpg', value: 10 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'newPpg', value: 1.50 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'tradePpg', value: 1.50 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'milesDriven', value: 100 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'milesDrivenTimeframe', value: 'month' }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'newMpg', value: 20 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'tradeMpg', value: 10 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'newPpg', value: 1.50 }, + { type: ActionTypes.SAVE_FUEL_SAVINGS, dateModified, settings: store.getState() }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'tradePpg', value: 1.50 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'milesDriven', value: 100 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'milesDrivenTimeframe', value: 'week' }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'newMpg', value: 20 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'tradeMpg', value: 10 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'newPpg', value: 1.50 } ]; actions.forEach(action => store.dispatch(action)); const lastGoodSavings = calculator().calculateSavings(store.getState().fuelSavings); const moreActions = [ - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'tradePpg', value: 0 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'milesDriven', value: 100 }, - { type: ActionTypes.CALCULATE_FUEL_SAVINGS, settings: store.getState(), fieldName: 'milesDrivenTimeframe', value: 'year' } + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'tradePpg', value: 0 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'milesDriven', value: 100 }, + { type: ActionTypes.CALCULATE_FUEL_SAVINGS, dateModified, settings: store.getState(), fieldName: 'milesDrivenTimeframe', value: 'year' } ]; // actions.push(...moreActions); @@ -114,7 +124,7 @@ describe('Store', () => { milesDriven: 100, milesDrivenTimeframe: 'year', displayResults: false, - dateModified: dateHelper.getFormattedDateTime(new Date()), + dateModified, necessaryDataIsProvidedToCalculateSavings: false, savings: lastGoodSavings }; diff --git a/src/utils/dateHelper.js b/src/utils/dateHelper.js index c1aea94be..7ec5d64e5 100644 --- a/src/utils/dateHelper.js +++ b/src/utils/dateHelper.js @@ -1,6 +1,6 @@ export default class DateHelper { // See tests for desired format. - static getFormattedDateTime(date) { + static getFormattedDateTime(date = new Date()) { return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${this.padLeadingZero(date.getMinutes())}:${this.padLeadingZero(date.getSeconds())}`; }