From 2b01d4f338f4c59ad6c37f0eb856ebc65314d653 Mon Sep 17 00:00:00 2001 From: Richard Scarrott Date: Sat, 22 Oct 2016 21:06:24 +0100 Subject: [PATCH 1/4] Add tests for utils --- .travis.yml | 3 + README.md | 2 +- package.json | 1 + src/actions/quote/quote.js | 3 +- src/actions/quote/quote.test.js | 4 +- src/utils/{ => fetch}/fetch.js | 12 +-- src/utils/fetch/fetch.test.js | 106 +++++++++++++++++++ src/utils/{ => networkerror}/NetworkError.js | 2 +- src/utils/networkerror/NetworkError.test.js | 29 +++++ 9 files changed, 151 insertions(+), 11 deletions(-) rename src/utils/{ => fetch}/fetch.js (87%) create mode 100644 src/utils/fetch/fetch.test.js rename src/utils/{ => networkerror}/NetworkError.js (80%) create mode 100644 src/utils/networkerror/NetworkError.test.js diff --git a/.travis.yml b/.travis.yml index 50719e0..dab01e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,9 @@ node_js: cache: directories: - node_modules +branches: + only: + - master sudo: false branches: only: diff --git a/README.md b/README.md index b9445e7..7c40994 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 60fram.es React Boilerplate [![Build Status](https://travis-ci.org/60frames/react-boilerplate.svg?branch=master)](https://travis-ci.org/60frames/react-boilerplate) +# 60fram.es React Boilerplate [![Build Status](https://travis-ci.org/60frames/react-boilerplate.svg?branch=master)](https://travis-ci.org/60frames/react-boilerplate) [![Coverage Status](https://coveralls.io/repos/github/60frames/react-boilerplate/badge.svg?branch=master)](https://coveralls.io/github/60frames/react-boilerplate?branch=master) Production-ready boilerplate for building *universal* web apps with [React](https://github.com/facebook/react) and [Redux](https://github.com/reactjs/redux) diff --git a/package.json b/package.json index 8a450f8..652b5da 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "compression": "^1.6.1", "css-reset": "60frames/css-reset.git#1.0.0", "debug": "^2.2.0", + "es6-error": "^4.0.0", "express": "^4.13.4", "isomorphic-fetch": "^2.2.1", "ms": "^0.7.1", diff --git a/src/actions/quote/quote.js b/src/actions/quote/quote.js index f8bf329..8104b25 100644 --- a/src/actions/quote/quote.js +++ b/src/actions/quote/quote.js @@ -1,10 +1,11 @@ -import fetch from 'utils/fetch'; +import fetch from 'utils/fetch/fetch'; export const FETCH_QUOTE_REQUEST = 'FETCH_QUOTE_REQUEST'; export const FETCH_QUOTE_SUCCESS = 'FETCH_QUOTE_SUCCESS'; export const FETCH_QUOTE_FAILURE = 'FETCH_QUOTE_FAILURE'; function fetchQuoteFailure(error) { + debugger return { type: FETCH_QUOTE_FAILURE, payload: error.message diff --git a/src/actions/quote/quote.test.js b/src/actions/quote/quote.test.js index d125b2a..b1396a1 100644 --- a/src/actions/quote/quote.test.js +++ b/src/actions/quote/quote.test.js @@ -1,11 +1,11 @@ /* global jest */ /* eslint-env jasmine */ -jest.mock('utils/fetch'); +jest.mock('utils/fetch/fetch'); import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import fetch from 'utils/fetch'; +import fetch from 'utils/fetch/fetch'; import { FETCH_QUOTE_REQUEST, FETCH_QUOTE_SUCCESS, diff --git a/src/utils/fetch.js b/src/utils/fetch/fetch.js similarity index 87% rename from src/utils/fetch.js rename to src/utils/fetch/fetch.js index a203bee..5b27b88 100644 --- a/src/utils/fetch.js +++ b/src/utils/fetch/fetch.js @@ -1,5 +1,5 @@ -import fetch from 'isomorphic-fetch'; -import NetworkError from 'utils/NetworkError'; +import isomorphicFetch from 'isomorphic-fetch'; +import NetworkError from 'utils/networkerror/NetworkError'; function checkStatus(response) { if (response.status >= 200 && response.status < 300) { @@ -35,7 +35,7 @@ function parseError(response) { * Wrapper around `window.fetch` to handle json parsing and offer a * consistent error interface. * - * xhr('/Users') + * fetch('/Users') * .then(json => { * console.log('success', json); * }, networkError => { @@ -48,7 +48,7 @@ function parseError(response) { * @param {Object} options https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch * @return {Promise} */ -function xhr(url, options = {}) { +function fetch(url, options = {}) { options = Object.assign({ headers: { 'Accept': 'application/json', @@ -56,9 +56,9 @@ function xhr(url, options = {}) { } }, options); - return fetch(url, options) + return isomorphicFetch(url, options) .then(checkStatus) .then(parseSuccess, parseError); } -export default xhr; +export default fetch; diff --git a/src/utils/fetch/fetch.test.js b/src/utils/fetch/fetch.test.js new file mode 100644 index 0000000..33d7aeb --- /dev/null +++ b/src/utils/fetch/fetch.test.js @@ -0,0 +1,106 @@ +jest.mock('isomorphic-fetch'); + +import isomorphicFetch from 'isomorphic-fetch'; +import fetch from 'utils/fetch/fetch'; + +describe('utils/fetch/fetch', () => { + + let throwFn; + + beforeEach(() => { + const response = new Response('{}', { status: 200 , statusText: 'OKs' }); + isomorphicFetch.mockClear(); + isomorphicFetch.mockReturnValue(Promise.resolve(response)); + throwFn = jest.fn(() => { throw new Error('expected `throwFn` not to be called') }); + }); + + it('returns a promise', () => { + expect(fetch('http://foo.com/bar')).toBeInstanceOf(Promise); + }); + + it('uses json headers by default', () => { + fetch('http://foo.com/bar'); + expect(isomorphicFetch).toBeCalledWith('http://foo.com/bar', { + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + }); + }); + + describe('successful responses', () => { + + it('handles valid json responses', () => { + const response = new Response('{ "foo": "bar" }', { status: 200, statusText: 'OKs' }); + isomorphicFetch.mockReturnValue(Promise.resolve(response)); + return fetch('http://foo.com/bar') + .then(data => { + expect(data).toEqual({ foo: 'bar' }); + }); + }); + + it('handles invalid json responses', () => { + const response = new Response('{ invalid json', { status: 200, statusText: 'OKs' }); + isomorphicFetch.mockReturnValue(Promise.resolve(response)); + return fetch('http://foo.com/bar') + .then(throwFn, error => { + expect(error.message).toBe('Invalid JSON Response'); + }); + }); + + [{ + status: 201, + statusText: 'Created' + }, { + status: 204, + statusText: 'No Content' + }].forEach(({ status, statusText }) => { + it(`handles ${status} responses`, () => { + const response = new Response(void 0, { status, statusText }); + isomorphicFetch.mockReturnValue(Promise.resolve(response)); + return fetch('http://foo.com/bar') + .then(data => { + expect(data).toEqual({}); + }); + }); + }); + + }); + + describe('unsuccessful responses', () => { + + it('handles valid json responses', () => { + const response = new Response('{ "foo": "bar" }', { status: 400, statusText: 'Bad Request' }); + isomorphicFetch.mockReturnValue(Promise.resolve(response)); + return fetch('http://foo.com/bar') + .then(throwFn, error => { + expect(error.status).toBe(400); + expect(error.message).toBe('Bad Request'); + expect(error.response).toEqual({ foo: 'bar' }); + }); + }); + + it('handles invalid json responses', () => { + const response = new Response('{ invalid json', { status: 400, statusText: 'Bad Request' }); + isomorphicFetch.mockReturnValue(Promise.resolve(response)); + return fetch('http://foo.com/bar') + .then(throwFn, error => { + expect(error.status).toBe(400); + expect(error.message).toBe('Bad Request'); + expect(error.response).toEqual({}); + }); + }); + + it('handles offline / cors errors', () => { + isomorphicFetch.mockReturnValue(Promise.resolve({ message: 'Failed to fetch' })); + return fetch('http://foo.com/bar') + .then(throwFn, error => { + expect(error.status).toBe(void 0); + expect(error.message).toBe('Failed to fetch'); + expect(error.response).toEqual({}); + }); + }); + + }); + +}); diff --git a/src/utils/NetworkError.js b/src/utils/networkerror/NetworkError.js similarity index 80% rename from src/utils/NetworkError.js rename to src/utils/networkerror/NetworkError.js index e51b050..707d848 100644 --- a/src/utils/NetworkError.js +++ b/src/utils/networkerror/NetworkError.js @@ -1,4 +1,4 @@ -import ExtendableError from 'utils/ExtendableError'; +import ExtendableError from 'es6-error'; class NetworkError extends ExtendableError { constructor(message, status, json = {}) { diff --git a/src/utils/networkerror/NetworkError.test.js b/src/utils/networkerror/NetworkError.test.js new file mode 100644 index 0000000..54417fb --- /dev/null +++ b/src/utils/networkerror/NetworkError.test.js @@ -0,0 +1,29 @@ +import NetworkError from 'utils/networkerror/NetworkError'; + +describe('utils/networkerror/NetworkError', () => { + + it('extends `Error`', () => { + expect(new NetworkError()).toBeInstanceOf(Error); + }); + + it('defines `message`', () => { + expect(new NetworkError('Foo bar').message).toBe('Foo bar'); + }); + + it('defines `status`', () => { + expect(new NetworkError(void 0, 404).status).toBe(404); + }); + + it('defines `response`', () => { + expect(new NetworkError(void 0, 404, { + foo: 'bar' + }).response).toEqual({ + foo: 'bar' + }); + }); + + it('`response` defaults to an object', () => { + expect(new NetworkError().response).toEqual({}); + }); + +}); From b1a72e815cf81fd7195ac57074a63b09f8ddaf19 Mon Sep 17 00:00:00 2001 From: Richard Scarrott Date: Sat, 22 Oct 2016 21:09:52 +0100 Subject: [PATCH 2/4] fixup! Add tests for utils --- src/actions/quote/quote.js | 1 - src/components/notfound/NotFound.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/actions/quote/quote.js b/src/actions/quote/quote.js index 8104b25..cd4659e 100644 --- a/src/actions/quote/quote.js +++ b/src/actions/quote/quote.js @@ -5,7 +5,6 @@ export const FETCH_QUOTE_SUCCESS = 'FETCH_QUOTE_SUCCESS'; export const FETCH_QUOTE_FAILURE = 'FETCH_QUOTE_FAILURE'; function fetchQuoteFailure(error) { - debugger return { type: FETCH_QUOTE_FAILURE, payload: error.message diff --git a/src/components/notfound/NotFound.js b/src/components/notfound/NotFound.js index 4768452..6290931 100644 --- a/src/components/notfound/NotFound.js +++ b/src/components/notfound/NotFound.js @@ -1,5 +1,5 @@ import React from 'react'; -import styles from './NotFound.css'; +import styles from 'components/notfound/NotFound.css'; function NotFound() { return ( From fe9b2197caa387c8de5aa0c0b1aa0bc69e39f030 Mon Sep 17 00:00:00 2001 From: Richard Scarrott Date: Sat, 22 Oct 2016 21:11:41 +0100 Subject: [PATCH 3/4] fixup! Add tests for utils --- src/utils/ExtendableError.js | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 src/utils/ExtendableError.js diff --git a/src/utils/ExtendableError.js b/src/utils/ExtendableError.js deleted file mode 100644 index fdc42b1..0000000 --- a/src/utils/ExtendableError.js +++ /dev/null @@ -1,11 +0,0 @@ -// http://stackoverflow.com/questions/31089801/extending-error-in-javascript-with-es6-syntax -class ExtendableError extends Error { - constructor(message) { - super(); - this.message = message; - this.stack = (new Error()).stack; - this.name = this.constructor.name; - } -} - -export default ExtendableError; From 35c548fc3c0bf5656cd5e386f6430c55d03ca46c Mon Sep 17 00:00:00 2001 From: Richard Scarrott Date: Sat, 22 Oct 2016 21:28:23 +0100 Subject: [PATCH 4/4] fixup! Add tests for utils --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index dab01e2..d90a4d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,6 @@ branches: only: - master sudo: false -branches: - only: - - master script: - npm test - npm run build