diff --git a/.env_test b/.env_test
new file mode 100644
index 0000000..851861a
--- /dev/null
+++ b/.env_test
@@ -0,0 +1 @@
+CI="true"
diff --git a/.example.env b/.example.env
index 8c10d90..de52b4a 100644
--- a/.example.env
+++ b/.example.env
@@ -1,6 +1,5 @@
-REACT_APP_SERVICE_ID=7891245607
-REACT_APP_GONEBUSY_TOKEN="Token af9094c6d46658e60cde12e34ad26979"
-REACT_APP_API_HOST="http://sandbox.gonebusy.com"
-REACT_APP_API_PATH="/api/v1"
-REACT_APP_IS_PROXIED="true"
-REACT_APP_PROXY_HOST="http://localhost:3000"
+GONEBUSY_TOKEN="Token af9094c6d46658e60cde12e34ad26979"
+GONEBUSY_API_HOST="http://sandbox.gonebusy.com"
+GONEBUSY_API_PATH="/api/v1"
+GONEBUSY_IS_PROXIED="true"
+GONEBUSY_PROXY_HOST="http://localhost:3000"
diff --git a/.example.env_test b/.example.env_test
new file mode 100644
index 0000000..851861a
--- /dev/null
+++ b/.example.env_test
@@ -0,0 +1 @@
+CI="true"
diff --git a/config/env.js b/config/env.js
index 5d0ab7b..383b9fe 100644
--- a/config/env.js
+++ b/config/env.js
@@ -2,19 +2,22 @@
// injected into the application via DefinePlugin in Webpack configuration.
var REACT_APP = /^REACT_APP_/i;
+var clientParams = require('./gonebusy_env').client;
function getClientEnvironment(publicUrl) {
+ var envData = Object.assign({}, process.env, clientParams);
+
var processEnv = Object
- .keys(process.env)
+ .keys(envData)
.filter(key => REACT_APP.test(key))
.reduce((env, key) => {
- env[key] = JSON.stringify(process.env[key]);
+ env[key] = JSON.stringify(envData[key]);
return env;
}, {
// Useful for determining whether we’re running in production mode.
// Most importantly, it switches React into the correct mode.
'NODE_ENV': JSON.stringify(
- process.env.NODE_ENV || 'development'
+ envData.NODE_ENV || 'development'
),
// Useful for resolving the correct path to static assets in `public`.
// For example,
.
diff --git a/config/gonebusy_env.js b/config/gonebusy_env.js
index b467b7e..113b824 100644
--- a/config/gonebusy_env.js
+++ b/config/gonebusy_env.js
@@ -1,26 +1,28 @@
const url = require('url');
const env = process.env;
-const reactAppServiceId = env['REACT_APP_SERVICE_ID'];
-const reactAppGonebusyToken = env['REACT_APP_GONEBUSY_TOKEN'];
-const gonebusyApiHost = env['REACT_APP_API_HOST'];
-const gonebusyApiPath = env['REACT_APP_API_PATH'];
-const gonebusyIsProxied = env['REACT_APP_IS_PROXIED'];
-const gonebusyProxyHost = env['REACT_APP_PROXY_HOST'];
-const is_proxied = !!(gonebusyIsProxied && JSON.parse(gonebusyIsProxied));
+const envToken = env['GONEBUSY_TOKEN'];
+const envApiHost = env['GONEBUSY_API_HOST'];
+const envApiPath = env['GONEBUSY_API_PATH'];
+const envIsProxied = env['GONEBUSY_IS_PROXIED'];
+const envProxyHost = env['GONEBUSY_PROXY_HOST'];
-const clientApiEndpoint = url.resolve((is_proxied ? gonebusyProxyHost : gonebusyApiHost) || '', gonebusyApiPath);
-const clientToken = is_proxied ? 'none' : reactAppGonebusyToken;
-const middlewareProxyHost = is_proxied ? gonebusyApiHost : undefined;
-const middlewareToken = is_proxied ? reactAppGonebusyToken : undefined;
+const is_proxied = !!(envIsProxied && JSON.parse(envIsProxied));
-console.log("to change the way we process .env so that it won't appear in plain JS", is_proxied);
+const clientApiEndpoint = url.resolve((is_proxied ? envProxyHost : envApiHost) || '', envApiPath);
+const clientToken = is_proxied ? 'none' : envToken;
+
+const middlewareProxyHost = is_proxied ? envApiHost : undefined;
+const middlewareToken = is_proxied ? envToken : undefined;
module.exports = {
- service_id: reactAppServiceId,
- clientApiEndpoint,
- clientToken,
- middlewareProxyHost,
- middlewarePath: gonebusyApiPath,
- middlewareToken,
+ client: {
+ REACT_APP_API_ENDPOINT: clientApiEndpoint,
+ REACT_APP_TOKEN: clientToken
+ },
+ middleware: {
+ proxy: middlewareProxyHost,
+ path: envApiPath,
+ token: middlewareToken
+ }
};
diff --git a/package.json b/package.json
index b9b09b4..2755c98 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,9 @@
"path-exists": "2.1.0",
"postcss-loader": "1.0.0",
"promise": "7.1.1",
+ "react-addons-test-utils": "^15.4.2",
"react-dev-utils": "^0.4.2",
+ "react-test-renderer": "^15.4.2",
"recursive-readdir": "2.1.0",
"strip-ansi": "3.0.1",
"style-loader": "0.13.1",
diff --git a/scripts/start.js b/scripts/start.js
index 1336e60..ecb236b 100644
--- a/scripts/start.js
+++ b/scripts/start.js
@@ -156,10 +156,10 @@ function addMiddleware(devServer) {
// `proxy` lets you to specify a fallback server during development.
// Every unrecognized request will be forwarded to it.
- const gonebusy_env = require('../config/gonebusy_env');
- const proxy = gonebusy_env['middlewareProxyHost'];
- const token = gonebusy_env['middlewareToken'];
- const middlewarePath = gonebusy_env['middlewarePath'];
+ var envParams = require('../config/gonebusy_env').middleware;
+ var proxy = envParams.proxy;
+ var token = envParams.token;
+ var apiPath = envParams.path;
devServer.use(historyApiFallback({
// Paths with dots should still use the history fallback.
@@ -192,7 +192,7 @@ function addMiddleware(devServer) {
// Tip: use https://jex.im/regulex/ to visualize the regex
// var mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/;
// var mayProxy = /^\/api.*$/;
- var mayProxy = new RegExp('^' + middlewarePath + '.*$');
+ var mayProxy = new RegExp('^' + apiPath + '.*$');
// Pass the scope regex both to Express and to the middleware for proxying
// of both HTTP and WebSockets to work without false positives.
@@ -204,13 +204,13 @@ function addMiddleware(devServer) {
proxyReq.setHeader('authorization', token);
console.log(`requested: [${req.method}] ${req.url}`);
console.log('query:', req.query);
- console.log('-\n');
+ console.log('-');
},
onProxyRes: (proxyRes, req, res) => {
proxyRes.on('data', (chunk) => {
console.log(`response for: [${req.method}] ${req.url}`);
console.log((new StringDecoder('utf8')).write(chunk));
- console.log('-\n');
+ console.log('-');
});
},
// pathRewrite: {
diff --git a/scripts/test.js b/scripts/test.js
index c4dc347..6a78928 100644
--- a/scripts/test.js
+++ b/scripts/test.js
@@ -5,13 +5,19 @@ process.env.PUBLIC_URL = '';
// if this file is missing. dotenv will never modify any environment variables
// that have already been set.
// https://github.com/motdotla/dotenv
-require('dotenv').config({silent: true});
+require('dotenv').config({
+ silent: true,
+ path: './.env_test'
+});
const jest = require('jest');
const argv = process.argv.slice(2);
+// console.log(process.env);
+
// Watch unless on CI or in coverage mode
if (!process.env.CI && argv.indexOf('--coverage') < 0) {
+ console.log('got inside');
argv.push('--watch');
}
diff --git a/src/__tests__sample/Bookie.js b/src/__tests__sample/Bookie.js
new file mode 100644
index 0000000..23a4522
--- /dev/null
+++ b/src/__tests__sample/Bookie.js
@@ -0,0 +1,19 @@
+jest.dontMock('../Bookie.jsx');
+
+describe('Bookie', function () {
+ var React = require('react');
+ var ReactDOM = require('react-dom');
+ var TestUtils = require('react-addons-test-utils');
+
+ var Bookie;
+
+ beforeEach(function () {
+ Bookie = require('../Bookie').default;
+ });
+
+ it('should exists', function () {
+ // Render into document
+ var bookie = TestUtils.renderIntoDocument();
+ expect(TestUtils.isCompositeComponent(bookie)).toBeTruthy();
+ });
+});
\ No newline at end of file
diff --git a/src/components/App.test.jsx b/src/components/App.test.jsx
index b84af98..948f468 100644
--- a/src/components/App.test.jsx
+++ b/src/components/App.test.jsx
@@ -2,6 +2,9 @@ import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
+jest.mock('../lib/BusyAdapter');
+jest.mock('../lib/Scheduler');
+
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(, div);
diff --git a/src/components/Bookie.jsx b/src/components/Bookie.jsx
index 6640abf..023e9aa 100644
--- a/src/components/Bookie.jsx
+++ b/src/components/Bookie.jsx
@@ -75,7 +75,7 @@ class Bookie extends Component {
pickerUpdater.adjust();
if (setLoading)
this.setParentLoading(pickerUpdater.state().loading);
- console.log('applying diff', diff, pickerUpdater.diff());
+ // console.log('applying diff', diff, pickerUpdater.diff());
this.setState(pickerUpdater.diff(), () => { this.pullMissingData(); });
}
}
diff --git a/src/components/Bookie.test.data.js b/src/components/Bookie.test.data.js
new file mode 100644
index 0000000..a4e4ede
--- /dev/null
+++ b/src/components/Bookie.test.data.js
@@ -0,0 +1,176 @@
+const savedState = {
+ "initialized": true,
+ "loading": false,
+ "daysFrameStart": "2017-01-01",
+ "dayPicked": "2017-01-01",
+ "hourPicked": 18,
+ "minutesIdxPicked": 1,
+ "daysToFetch": [],
+ "dayData": {
+ "2017-01-01": {
+ "presentSlots": {
+ "9": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "10": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "11": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "12": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "13": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "14": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "15": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "16": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "18": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "19": [
+ 0,
+ 15,
+ 30,
+ 45
+ ]
+ },
+ "presentHours": [
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 18,
+ 19
+ ]
+ },
+ "2017-01-02": {
+ "presentSlots": {
+ "9": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "10": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "11": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "12": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "13": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "14": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "15": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "16": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "17": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "18": [
+ 0,
+ 15,
+ 30,
+ 45
+ ],
+ "19": [
+ 0,
+ 15,
+ 30,
+ 45
+ ]
+ },
+ "presentHours": [
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19
+ ]
+ }
+ },
+ "startVal": "2017-01-01T18:15:00+02:00",
+ "endVal": "2017-01-01T19:00:00+02:00",
+};
+
+module.exports = {
+ savedState
+};
diff --git a/src/components/Bookie.test.jsx b/src/components/Bookie.test.jsx
new file mode 100644
index 0000000..f3d7e5d
--- /dev/null
+++ b/src/components/Bookie.test.jsx
@@ -0,0 +1,64 @@
+const React = require('react');
+const ReactDOM = require('react-dom');
+import ReactDOMServer from 'react-dom/server';
+
+const TestUtils = require('react-addons-test-utils');
+const Bookie = require('./Bookie').default;
+const renderer = require('react-test-renderer');
+
+jest.mock('../lib/Scheduler');
+jest.mock('../lib/BusyAdapter');
+
+it('should exists', () => {
+ const bookie = TestUtils.renderIntoDocument();
+ expect(TestUtils.isCompositeComponent(bookie)).toBeTruthy();
+});
+
+// it('markup matches snapshot', () => {
+// const tree = renderer.create().toJSON();
+// expect(tree).toMatchSnapshot();
+// });
+
+describe('enabling bookie button', () => {
+ // there's a gap on 2017-01-01 at 17:00-18:00
+ const savedState = require('./Bookie.test.data').savedState;
+
+ const createBookieWithState = function(state) {
+ const result = TestUtils.renderIntoDocument();
+ result.setState(state);
+ result.negotiateStateDiff({}, true);
+ return result;
+ }
+
+ it('is enabled for valid range', () => {
+ const bookieComponent = createBookieWithState(savedState);
+
+ // ensure booking is enabled in state
+ expect(bookieComponent.state.bookingAllowed).toBe(true);
+
+ // ensure booking button is enabled
+ expect(ReactDOM.findDOMNode(bookieComponent).querySelector('button').disabled).toBe(false);
+
+ // check markup snapshot
+ const reactElement = bookieComponent.render();
+ const renderedMarkup = ReactDOMServer.renderToStaticMarkup(reactElement);
+ expect(renderedMarkup).toMatchSnapshot();
+ });
+
+ it('is disabled for the range with a gap', () => {
+ const state = Object.assign({}, savedState, {
+ "hourPicked": 16,
+ "minutesIdxPicked": 2,
+ "endVal": "2017-01-01T18:15:00+02:00",
+ });
+
+ const bookieComponent = createBookieWithState(state);
+
+ expect(bookieComponent.state.bookingAllowed).toBe(false);
+ expect(ReactDOM.findDOMNode(bookieComponent).querySelector('button').disabled).toBe(true);
+
+ const reactElement = bookieComponent.render();
+ const renderedMarkup = ReactDOMServer.renderToStaticMarkup(reactElement);
+ expect(renderedMarkup).toMatchSnapshot();
+ });
+});
\ No newline at end of file
diff --git a/src/components/__snapshots__/Bookie.test.jsx.snap b/src/components/__snapshots__/Bookie.test.jsx.snap
new file mode 100644
index 0000000..9dd94c0
--- /dev/null
+++ b/src/components/__snapshots__/Bookie.test.jsx.snap
@@ -0,0 +1,3 @@
+exports[`enabling bookie button is disabled for the range with a gap 1`] = `"
01-01 4pm:30 — 01-01 6pm:15
"`;
+
+exports[`enabling bookie button is enabled for valid range 1`] = `"01-01 6pm:15 — 01-01 7pm:00
"`;
diff --git a/src/lib/BusyAdapter.js b/src/lib/BusyAdapter.js
index 4bb690d..29d73ae 100644
--- a/src/lib/BusyAdapter.js
+++ b/src/lib/BusyAdapter.js
@@ -2,15 +2,10 @@ import gonebusy, { CreateBookingBody } from 'gonebusy-nodejs-client/lib';
import { Promise } from 'bluebird';
import Scheduler from './Scheduler';
-import gonebusyEnv from '../../config/gonebusy_env';
-
const ServicesController = Promise.promisifyAll(gonebusy.ServicesController);
const BookingsController = Promise.promisifyAll(gonebusy.BookingsController);
-const {
- clientToken: authorization,
- clientApiEndpoint
-} = gonebusyEnv;
+const { REACT_APP_TOKEN: authorization, REACT_APP_API_ENDPOINT: clientApiEndpoint } = process.env;
gonebusy.configuration.BASEURI = clientApiEndpoint;
diff --git a/src/lib/Scheduler.js b/src/lib/Scheduler.js
index a3de043..eec816d 100644
--- a/src/lib/Scheduler.js
+++ b/src/lib/Scheduler.js
@@ -21,6 +21,10 @@ moment.updateLocale(moment.locale(), {
});
class Scheduler {
+ static getCurrentMoment() {
+ return moment();
+ }
+
static getDaysFrame(frameStartDate) {
const mbase = moment.utc(frameStartDate);
const result = [];
@@ -80,7 +84,7 @@ class Scheduler {
}
static getNowStr() {
- return moment().startOf('minute').format();
+ return Scheduler.getCurrentMoment().startOf('minute').format();
}
static getNextDayString(date) {
@@ -102,7 +106,7 @@ class Scheduler {
let result = '';
if (dateStr) {
const mVal = moment(dateStr);
- const mToday = moment();
+ const mToday = Scheduler.getCurrentMoment();
if (formatDayToString(mToday) === formatDayToString(mVal))
result = mVal.format('ha:mm');
else if (mToday.year() === mVal.year())
diff --git a/src/lib/StateUpdaterForDatePicker.js b/src/lib/StateUpdaterForDatePicker.js
index 0a1cb51..52d10b8 100644
--- a/src/lib/StateUpdaterForDatePicker.js
+++ b/src/lib/StateUpdaterForDatePicker.js
@@ -257,8 +257,8 @@ class StateUpdaterForDatePicker extends StateUpdaterBase {
gapFound = !dayData ||
!dayData.presentSlots[hour] ||
!~dayData.presentSlots[hour].indexOf(qMinIdx * 15);
- if (gapFound)
- console.log('gap found', nextMomentStr);
+ // if (gapFound)
+ // console.log('gap found', nextMomentStr);
} while (!gapFound && Scheduler.isAfterMin(day, hour, qMinIdx * 15, endVal));
bookingAllowed = !gapFound;
}
diff --git a/src/lib/__mocks__/BusyAdapter.js b/src/lib/__mocks__/BusyAdapter.js
new file mode 100644
index 0000000..c6e3d5a
--- /dev/null
+++ b/src/lib/__mocks__/BusyAdapter.js
@@ -0,0 +1,20 @@
+// const m = jest.genMockFromModule('../BusyAdapter');
+
+const mockData = require('./BusyAdapter.test.data');
+
+class BusyAdapterMock {
+
+ static getServiceInfoAsync() {
+ return new Promise((resolve) => {
+ resolve(mockData.serviceInfo);
+ });
+ }
+
+ static getSlotsAsync(date) {
+ return new Promise((resolve) => {
+ resolve(mockData.slotsReturned);
+ });
+ }
+}
+
+export default BusyAdapterMock;
diff --git a/src/lib/__mocks__/BusyAdapter.test.data.js b/src/lib/__mocks__/BusyAdapter.test.data.js
new file mode 100644
index 0000000..e45c930
--- /dev/null
+++ b/src/lib/__mocks__/BusyAdapter.test.data.js
@@ -0,0 +1,101 @@
+const serviceInfo = {
+ // id: 7891245607,
+ // name: "Dog walking"
+ id: 111000022,
+ name: "Service Name for tests 11"
+};
+
+const slotsReturned = [
+ "2017-01-02T09:00:00Z",
+ "2017-01-02T09:15:00Z",
+ "2017-01-02T09:30:00Z",
+ "2017-01-02T09:45:00Z",
+ "2017-01-02T10:00:00Z",
+ "2017-01-02T10:15:00Z",
+ "2017-01-02T10:30:00Z",
+ "2017-01-02T10:45:00Z",
+ "2017-01-02T11:00:00Z",
+ "2017-01-02T11:15:00Z",
+ "2017-01-02T11:30:00Z",
+ "2017-01-02T11:45:00Z",
+ "2017-01-02T12:00:00Z",
+ "2017-01-02T12:15:00Z",
+ "2017-01-02T12:30:00Z",
+ "2017-01-02T12:45:00Z",
+ "2017-01-02T13:00:00Z",
+ "2017-01-02T13:15:00Z",
+ "2017-01-02T13:30:00Z",
+ "2017-01-02T13:45:00Z",
+ "2017-01-02T14:00:00Z",
+ "2017-01-02T14:15:00Z",
+ "2017-01-02T14:30:00Z",
+ "2017-01-02T14:45:00Z",
+ "2017-01-02T15:00:00Z",
+ "2017-01-02T15:15:00Z",
+ "2017-01-02T15:30:00Z",
+ "2017-01-02T15:45:00Z",
+ "2017-01-02T16:00:00Z",
+ "2017-01-02T16:15:00Z",
+ "2017-01-02T16:30:00Z",
+ "2017-01-02T16:45:00Z",
+ "2017-01-02T17:00:00Z",
+ "2017-01-02T17:15:00Z",
+ "2017-01-02T17:30:00Z",
+ "2017-01-02T17:45:00Z",
+ "2017-01-02T18:00:00Z",
+ "2017-01-02T18:15:00Z",
+ "2017-01-02T18:30:00Z",
+ "2017-01-02T18:45:00Z",
+ "2017-01-02T19:00:00Z",
+ "2017-01-02T19:15:00Z",
+ "2017-01-02T19:30:00Z",
+ "2017-01-02T19:45:00Z",
+ "2017-01-03T09:00:00Z",
+ "2017-01-03T09:15:00Z",
+ "2017-01-03T09:30:00Z",
+ "2017-01-03T09:45:00Z",
+ "2017-01-03T10:00:00Z",
+ "2017-01-03T10:15:00Z",
+ "2017-01-03T10:30:00Z",
+ "2017-01-03T10:45:00Z",
+ "2017-01-03T11:00:00Z",
+ "2017-01-03T11:15:00Z",
+ "2017-01-03T11:30:00Z",
+ "2017-01-03T11:45:00Z",
+ "2017-01-03T12:00:00Z",
+ "2017-01-03T12:15:00Z",
+ "2017-01-03T12:30:00Z",
+ "2017-01-03T12:45:00Z",
+ "2017-01-03T13:00:00Z",
+ "2017-01-03T13:15:00Z",
+ "2017-01-03T13:30:00Z",
+ "2017-01-03T13:45:00Z",
+ "2017-01-03T14:00:00Z",
+ "2017-01-03T14:15:00Z",
+ "2017-01-03T14:30:00Z",
+ "2017-01-03T14:45:00Z",
+ "2017-01-03T15:00:00Z",
+ "2017-01-03T15:15:00Z",
+ "2017-01-03T15:30:00Z",
+ "2017-01-03T15:45:00Z",
+ "2017-01-03T16:00:00Z",
+ "2017-01-03T16:15:00Z",
+ "2017-01-03T16:30:00Z",
+ "2017-01-03T16:45:00Z",
+ "2017-01-03T17:00:00Z",
+ "2017-01-03T17:15:00Z",
+ "2017-01-03T17:30:00Z",
+ "2017-01-03T17:45:00Z",
+ "2017-01-03T18:00:00Z",
+ "2017-01-03T18:15:00Z",
+ "2017-01-03T18:30:00Z",
+ "2017-01-03T18:45:00Z",
+ "2017-01-03T19:00:00Z",
+ "2017-01-03T19:15:00Z",
+ "2017-01-03T19:30:00Z",
+ "2017-01-03T19:45:00Z"];
+
+module.exports = {
+ serviceInfo,
+ slotsReturned
+};
diff --git a/src/lib/__mocks__/Scheduler.js b/src/lib/__mocks__/Scheduler.js
new file mode 100644
index 0000000..83d3006
--- /dev/null
+++ b/src/lib/__mocks__/Scheduler.js
@@ -0,0 +1,6 @@
+const moment = require('moment');
+const schedulerInstance = require.requireActual('../Scheduler').default;
+
+schedulerInstance.getCurrentMoment = (() => moment('2017-01-02 17:00'));
+
+export default schedulerInstance;