made eslint stricter - added unit tests to cover sample in README

This commit is contained in:
Camel Aissani 2016-07-10 00:58:03 +02:00
parent 6bfd3d42e9
commit 0014025be4
12 changed files with 1675 additions and 1481 deletions

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
coverage
dist

View File

@ -1,3 +1,25 @@
{
"parser": "babel-eslint"
"parser": "babel-eslint",
"rules": {
"indent": [
2,
4
],
"quotes": [
2,
"single"
],
"linebreak-style": [
2,
"unix"
],
"semi": [
2,
"always"
]
},
"env": {
"node": true,
"browser": true
}
}

View File

@ -7,7 +7,7 @@
# frontexpress
Minimalist front end router framework a la [express](http://expressjs.com/)
[![Build Status](https://travis-ci.org/camelaissani/frontexpress.svg?branch=master)](https://travis-ci.org/camelaissani/frontexpress)
[![Coverage Status](https://coveralls.io/repos/github/camelaissani/frontexpress/badge.svg?branch=master)](https://coveralls.io/github/camelaissani/frontexpress?branch=master)
@ -19,7 +19,7 @@ const app = frontexpress();
app.get('/', (req, res) => {
window.alert('Hello World');
});
// Once DOM is loaded start listening HTTP requests
document.addEventListener("DOMContentLoaded", (event) => {
app.listen();
@ -37,7 +37,7 @@ $ npm install frontexpress
The quickest way to get started with frontexpress is to clone [frontexpress sample](https://github.com/camelaissani/frontexpress) to generate an application as shown below:
Clone the git repository:
```bash
$ git clone git@github.com:camelaissani/frontexpress-sample.git
$ cd frontexpress-sample
@ -56,14 +56,14 @@ $ npm start
```
## Tests
Clone the git repository:
```bash
$ git clone git@github.com:camelaissani/frontexpress.git
$ cd frontexpress
```
Install the dependencies and run the test suite:
```bash
@ -77,34 +77,34 @@ $ npm test
Routing allows to link the frontend application with HTTP requests to a particular URI (or path).
The link can be specific to an HTTP request method (GET, POST, and so on).
The following examples illustrate how to define simple routes.
Listen an HTTP GET request on URI (/):
```js
app.get('/', (req, res) => {
window.alert('Hello World');
app.get('/', (req, res) => {
window.alert('Hello World');
});
```
Listen an HTTP POST request on URI (/):
```js
app.post('/', (req, res) => {
window.alert('Got a POST request at /');
app.post('/', (req, res) => {
window.alert('Got a POST request at /');
});
```
### Route paths
Route paths, in combination with a request method, define the endpoints at which requests can be made.
Route paths can be strings (see basic routing section), or regular expressions.
This route path matches all GET request paths which start with (/api/):
```js
app.get(/^api\//, (req, res) => {
app.get(/^api\//, (req, res) => {
console.log(`api was requested ${req.uri}`);
});
```
@ -139,9 +139,9 @@ You can create chainable route handlers for a route path by using ```app.route()
```js
app.route('/book')
.get((req, res) => { res.send('Get a random book') })
.post((req, res) => { res.send('Add a book') })
.put((req, res) => { res.send('Update the book') });
.get((req, res) => { console.log('Get a random book') })
.post((req, res) => { console.log('Add a book') })
.put((req, res) => { console.log('Update the book') });
```
### frontexpress.Router
@ -153,23 +153,23 @@ Create a router file named ```birds.js``` in the app directory, with the followi
```js
import frontexpress from 'frontexpress';
const router = frontexpress.Router();
const router = frontexpress.Router();
// middleware that is specific to this router
// middleware that is specific to this router
router.use((req, res, next) => {
console.log(`Time: ${Date.now()}`);
next();
});
console.log(`Time: ${Date.now()}`);
next();
});
// react on home page route
router.get('/', function(req, res) => {
// react on home page route
router.get('/', (req, res) => {
document.querySelector('.content').innerHTML = '<p>Birds home page</p>';
});
});
// react on about route
router.get('/about', (req, res) => {
document.querySelector('.content').innerHTML = '<p>About birds</p>';
});
// react on about route
router.get('/about', (req, res) => {
document.querySelector('.content').innerHTML = '<p>About birds</p>';
});
export default router;
```

View File

@ -1,244 +1,246 @@
import Router, {Route} from './router';
import Middleware from './middleware';
import Requester, {HTTP_METHODS} from './requester';
export default class Application {
constructor() {
this.routers = [];
this.requester = new Requester();
this.DOMLoading = false;
this.settingsDef = {
'http-requester': (requester) => {this.requester = requester}
};
}
////////////////////////////////////////////////////////
// Setting
set(name, value) {
const settingFn = this.settingsDef[name];
if (!settingFn) {
throw new ReferenceError(`unsupported setting ${name}`);
}
settingFn(value);
}
////////////////////////////////////////////////////////
// Routes
route(uri) {
const router = new Router(uri);
this.routers.push(router);
return router;
}
use(...args) {
if (args.length === 0) {
throw new TypeError(`use method takes at least a middleware or a router`);
}
let baseUri, middleware, router, which;
if (args.length === 1) {
[which,] = args;
} else {
[baseUri, which,] = args;
}
if (!(which instanceof Middleware) && (typeof which !== 'function') && !(which instanceof Router)) {
throw new TypeError(`use method takes at least a middleware or a router`);
}
if (which instanceof Router) {
router = which;
router.baseUri = baseUri;
} else {
middleware = which;
router = new Router(baseUri);
for (const method of Object.keys(HTTP_METHODS)) {
router[method.toLowerCase()](middleware);
}
}
this.routers.push(router);
}
listen(callback) {
document.onreadystatechange = () => {
const uri = window.location.pathname + window.location.search;
const method = 'GET';
const request = {method, uri};
const response = {status: 200, statusText: 'OK'};
// gathers all routes impacted by the current browser location
const currentRoutes = this._routes(uri, method);
// listen dom events
if (document.readyState === 'loading') {
this.DOMLoading = true;
this._callMiddlewareEntered(currentRoutes, request)
} else if (document.readyState === 'interactive') {
if (!this.DOMLoading) {
this._callMiddlewareEntered(currentRoutes, request);
}
this._callMiddlewareUpdated(currentRoutes, request, response);
if (callback) {
callback(request, response);
}
}
};
window.addEventListener('beforeunload', () => {
this._callMiddlewareExited();
});
}
_routes(uri, method) {
const currentRoutes = [];
for (const router of this.routers) {
const routes = router.routes(uri, method);
currentRoutes.push(...routes);
}
return currentRoutes;
}
_callMiddlewareEntered(currentRoutes, request) {
for (const route of currentRoutes) {
if (route.middleware.entered) {
route.middleware.entered(request);
}
if (route.middleware.next && !route.middleware.next()) {
break;
}
}
}
_callMiddlewareUpdated(currentRoutes, request, response) {
for (const route of currentRoutes) {
route.visited = request;
// calls middleware updated method
if (route.middleware.updated) {
route.middleware.updated(request, response);
if (route.middleware.next && !route.middleware.next()) {
break;
}
} else {
// calls middleware method
let breakMiddlewareLoop = true;
const next = () => {
breakMiddlewareLoop = false;
}
route.middleware(request, response, next);
if (breakMiddlewareLoop) {
break;
}
}
}
}
_callMiddlewareExited() {
// calls middleware exited method
for (const router of this.routers) {
const routes = router.visited();
for (const route of routes) {
if (route.middleware.exited) {
route.middleware.exited(route.visited);
route.visited = null;
}
}
}
}
_callMiddlewareFailed(currentRoutes, request, response) {
for (const route of currentRoutes) {
// calls middleware failed method
if (route.middleware.failed) {
route.middleware.failed(request, response);
if (route.middleware.next && !route.middleware.next()) {
break;
}
} else {
// calls middleware method
let breakMiddlewareLoop = true;
const next = () => {
breakMiddlewareLoop = false;
}
route.middleware(request, response, next);
if (breakMiddlewareLoop) {
break;
}
}
}
}
///////////////////////////////////////////////////////
// Ajax request
_fetch({method, uri, headers, data}, resolve, reject) {
// calls middleware exited method
this._callMiddlewareExited();
// gathers all routes impacted by the uri
const currentRoutes = this._routes(uri, method);
// calls middleware entered method
this._callMiddlewareEntered(currentRoutes, {method, uri, headers, data});
// invokes http request
this.requester.fetch({uri, method},
(request, response) => {
this._callMiddlewareUpdated(currentRoutes, request, response);
if (resolve) {
resolve(request, response);
}
},
(request, response) => {
this._callMiddlewareFailed(currentRoutes, request, response);
if (reject) {
reject(request, response);
}
});
}
}
Object.keys(HTTP_METHODS).reduce((reqProto, method) => {
// Middleware methods
const middlewareMethodeName = method.toLowerCase();
reqProto[middlewareMethodeName] = function(...args) {
if (args.length === 0) {
throw new TypeError(`${middlewareMethodeName} method takes at least a middleware`);
}
let baseUri, middleware, which;
if (args.length === 1) {
[which,] = args;
} else {
[baseUri, which,] = args;
}
if (!(which instanceof Middleware) && (typeof which !== 'function')) {
throw new TypeError(`${middlewareMethodeName} method takes at least a middleware`);
}
const router = new Router();
middleware = which;
router[middlewareMethodeName](baseUri, middleware);
this.routers.push(router);
}
// HTTP methods
const httpMethodName = 'http'+method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
reqProto[httpMethodName] = function(request, resolve, reject) {
let {uri, headers, data} = request;
if (!uri) {
uri = request;
}
return this._fetch({
uri,
method,
headers,
data
}, resolve, reject);
}
return reqProto;
import Router, {Route} from './router';
import Middleware from './middleware';
import Requester, {HTTP_METHODS} from './requester';
export default class Application {
constructor() {
this.routers = [];
this.requester = new Requester();
this.DOMLoading = false;
this.settingsDef = {
'http-requester': (requester) => {this.requester = requester;}
};
}
////////////////////////////////////////////////////////
// Setting
set(name, value) {
const settingFn = this.settingsDef[name];
if (!settingFn) {
throw new ReferenceError(`unsupported setting ${name}`);
}
settingFn(value);
}
////////////////////////////////////////////////////////
// Start listening requests
listen(callback) {
document.onreadystatechange = () => {
const uri = window.location.pathname + window.location.search;
const method = 'GET';
const request = {method, uri};
const response = {status: 200, statusText: 'OK'};
// gathers all routes impacted by the current browser location
const currentRoutes = this._routes(uri, method);
// listen dom events
if (document.readyState === 'loading') {
this.DOMLoading = true;
this._callMiddlewareEntered(currentRoutes, request);
} else if (document.readyState === 'interactive') {
if (!this.DOMLoading) {
this._callMiddlewareEntered(currentRoutes, request);
}
this._callMiddlewareUpdated(currentRoutes, request, response);
if (callback) {
callback(request, response);
}
}
};
window.addEventListener('beforeunload', () => {
this._callMiddlewareExited();
});
}
////////////////////////////////////////////////////////
// Routes
route(uri) {
const router = new Router(uri);
this.routers.push(router);
return router;
}
use(...args) {
if (args.length === 0) {
throw new TypeError('use method takes at least a middleware or a router');
}
let baseUri, middleware, router, which;
if (args.length === 1) {
[which,] = args;
} else {
[baseUri, which,] = args;
}
if (!(which instanceof Middleware) && (typeof which !== 'function') && !(which instanceof Router)) {
throw new TypeError('use method takes at least a middleware or a router');
}
if (which instanceof Router) {
router = which;
router.baseUri = baseUri;
} else {
middleware = which;
router = new Router(baseUri);
for (const method of Object.keys(HTTP_METHODS)) {
router[method.toLowerCase()](middleware);
}
}
this.routers.push(router);
}
_routes(uri, method) {
const currentRoutes = [];
for (const router of this.routers) {
const routes = router.routes(uri, method);
currentRoutes.push(...routes);
}
return currentRoutes;
}
_callMiddlewareEntered(currentRoutes, request) {
for (const route of currentRoutes) {
if (route.middleware.entered) {
route.middleware.entered(request);
}
if (route.middleware.next && !route.middleware.next()) {
break;
}
}
}
_callMiddlewareUpdated(currentRoutes, request, response) {
for (const route of currentRoutes) {
route.visited = request;
// calls middleware updated method
if (route.middleware.updated) {
route.middleware.updated(request, response);
if (route.middleware.next && !route.middleware.next()) {
break;
}
} else {
// calls middleware method
let breakMiddlewareLoop = true;
const next = () => {
breakMiddlewareLoop = false;
};
route.middleware(request, response, next);
if (breakMiddlewareLoop) {
break;
}
}
}
}
_callMiddlewareExited() {
// calls middleware exited method
for (const router of this.routers) {
const routes = router.visited();
for (const route of routes) {
if (route.middleware.exited) {
route.middleware.exited(route.visited);
route.visited = null;
}
}
}
}
_callMiddlewareFailed(currentRoutes, request, response) {
for (const route of currentRoutes) {
// calls middleware failed method
if (route.middleware.failed) {
route.middleware.failed(request, response);
if (route.middleware.next && !route.middleware.next()) {
break;
}
} else {
// calls middleware method
let breakMiddlewareLoop = true;
const next = () => {
breakMiddlewareLoop = false;
};
route.middleware(request, response, next);
if (breakMiddlewareLoop) {
break;
}
}
}
}
///////////////////////////////////////////////////////
// Ajax request
_fetch({method, uri, headers, data}, resolve, reject) {
// calls middleware exited method
this._callMiddlewareExited();
// gathers all routes impacted by the uri
const currentRoutes = this._routes(uri, method);
// calls middleware entered method
this._callMiddlewareEntered(currentRoutes, {method, uri, headers, data});
// invokes http request
this.requester.fetch({uri, method},
(request, response) => {
this._callMiddlewareUpdated(currentRoutes, request, response);
if (resolve) {
resolve(request, response);
}
},
(request, response) => {
this._callMiddlewareFailed(currentRoutes, request, response);
if (reject) {
reject(request, response);
}
});
}
}
Object.keys(HTTP_METHODS).reduce((reqProto, method) => {
// Middleware methods
const middlewareMethodeName = method.toLowerCase();
reqProto[middlewareMethodeName] = function(...args) {
if (args.length === 0) {
throw new TypeError(`${middlewareMethodeName} method takes at least a middleware`);
}
let baseUri, middleware, which;
if (args.length === 1) {
[which,] = args;
} else {
[baseUri, which,] = args;
}
if (!(which instanceof Middleware) && (typeof which !== 'function')) {
throw new TypeError(`${middlewareMethodeName} method takes at least a middleware`);
}
const router = new Router();
middleware = which;
router[middlewareMethodeName](baseUri, middleware);
this.routers.push(router);
};
// HTTP methods
const httpMethodName = 'http'+method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
reqProto[httpMethodName] = function(request, resolve, reject) {
let {uri, headers, data} = request;
if (!uri) {
uri = request;
}
return this._fetch({
uri,
method,
headers,
data
}, resolve, reject);
};
return reqProto;
}, Application.prototype);

View File

@ -1,21 +1,21 @@
export default class Middleware {
constructor(name='') {
this.name = name;
}
entered(request) {
}
exited(request) {
}
updated(request, response) {
}
failed(request, response) {
}
next() {
return true;
}
}
export default class Middleware {
constructor(name='') {
this.name = name;
}
entered(request) {
}
exited(request) {
}
updated(request, response) {
}
failed(request, response) {
}
next() {
return true;
}
}

View File

@ -5,7 +5,7 @@ export const HTTP_METHODS = {
return uri;
}
let anchor = ''
let anchor = '';
let uriWithoutAnchor = uri;
const hashIndex = uri.indexOf('#');
if (hashIndex >=1) {
@ -104,7 +104,7 @@ export default class Requester {
}
}
if (data) {
xmlhttp.send(data);
xmlhttp.send(data);
} else {
xmlhttp.send();
}

View File

@ -11,6 +11,10 @@ class Route {
}
get uri() {
if (!this.uriPart && !this.method) {
return undefined;
}
if (this.uriPart instanceof RegExp) {
return this.uriPart;
}
@ -69,6 +73,9 @@ export default class Router {
routes(uri, method) {
return this._routes.filter((route) => {
if (!route.uri && !route.method) {
return true;
}
if (route.method !== method) {
return false;
}
@ -105,9 +112,19 @@ export default class Router {
});
}
use(middleware) {
if (!(middleware instanceof Middleware) && (typeof middleware !== 'function') ) {
throw new TypeError('use method takes at least a middleware');
}
this._add(new Route(this, undefined, undefined, middleware));
return this;
}
all(...args) {
if (args.length === 0) {
throw new TypeError(`use all method takes at least a middleware`);
throw new TypeError('use all method takes at least a middleware');
}
let middleware;
@ -118,7 +135,7 @@ export default class Router {
}
if (!(middleware instanceof Middleware) && (typeof middleware !== 'function') ) {
throw new TypeError(`use all method takes at least a middleware`);
throw new TypeError('use all method takes at least a middleware');
}
for (const method of Object.keys(HTTP_METHODS)) {
@ -147,11 +164,11 @@ for (const method of Object.keys(HTTP_METHODS)) {
}
if (uri && this._baseUri && this._baseUri instanceof RegExp) {
throw new TypeError(`router contains a regexp cannot mix with route uri/regexp`);
throw new TypeError('router contains a regexp cannot mix with route uri/regexp');
}
this._add(new Route(this, uri, method, middleware));
return this;
}
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
/*eslint-env mocha*/
import {assert} from 'chai';
import sinon from 'sinon';
import frontexpress from '../lib/frontexpress';

151
test/readme-test.js Normal file
View File

@ -0,0 +1,151 @@
/*eslint-env mocha*/
import chai, {assert} from 'chai';
import sinon from 'sinon';
import frontexpress from '../lib/frontexpress';
import Requester from '../lib/requester';
describe('Test sample from README', () => {
let window, requester;
beforeEach(() => {
global.document = {};
global.window = {
location: {
pathname: '/',
search: ''
},
addEventListener(eventType, callback) {}
};
requester = new Requester();
sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => {
resolve(
{uri, method, headers, data},
{status: 200, statusText: 'OK', responseText:''}
);
});
});
it('main sample', (done) => {
const app = frontexpress();
// listen HTTP GET request on path (/)
app.get('/', (req, res) => {
done();
});
// start listening frontend application requests
app.listen();
//simulate readystatechange
document.readyState = 'interactive';
document.onreadystatechange();
});
it('route handlers', (done) => {
const spy_log = sinon.spy();
const h1 = (req, res, next) => { spy_log('h1!'); next(); };
const h2 = (req, res, next) => { spy_log('h2!');};
const h3 = (req, res, next) => { spy_log('h3!'); next(); };
const app = frontexpress();
app.set('http-requester', requester);
app.get('/example/a', h1);
app.get('/example/a', h2);
app.get('/example/a', h3);
// start listening frontend application requests
app.listen();
// simulate readystatechange
document.readyState = 'interactive';
document.onreadystatechange();
// make an ajax request on /example/a
app.httpGet('/example/a', (req, res) => {
assert(spy_log.calledTwice);
assert(spy_log.withArgs('h1!').calledOnce);
assert(spy_log.withArgs('h2!').calledOnce);
assert(spy_log.neverCalledWith('h3!'));
done();
});
});
it('app.route()', (done) => {
const spy_log = sinon.spy();
const app = frontexpress();
app.set('http-requester', requester);
app.route('/book')
.get((req, res) => { spy_log('Get a random book');})
.post((req, res) => { spy_log('Add a book');})
.put((req, res) => { spy_log('Update the book');});
// start listening frontend application requests
app.listen();
// simulate readystatechange
document.readyState = 'interactive';
document.onreadystatechange();
// make an ajax request on /book
app.httpGet('/book', (req, res) => {
app.httpPost('/book', (req, res) => {
app.httpPut('/book', (req, res) => {
// Yes I know Promise :-) Next evolution
assert(spy_log.callCount === 3);
assert(spy_log.withArgs('Get a random book').calledOnce);
assert(spy_log.withArgs('Add a book').calledOnce);
assert(spy_log.withArgs('Update the book').calledOnce);
done();
});
});
});
});
it('frontexpress.Router', (done) => {
const spy_log = sinon.spy();
const router = frontexpress.Router();
// middleware that is specific to this router
router.use((req, res, next) => {
spy_log(`Time: ${Date.now()}`);
next();
});
// react on home page route
router.get('/', (req, res) => {
spy_log('<p>Birds home page</p>');
});
// react on about route
router.get('/about', (req, res) => {
spy_log('<p>About birds</p>');
});
const app = frontexpress();
app.set('http-requester', requester);
app.use('/birds', router);
// make an ajax request on /birds
app.httpGet('/birds/', (req, res) => {
assert(spy_log.callCount === 2);
assert(spy_log.withArgs('<p>Birds home page</p>').calledOnce);
assert(spy_log.neverCalledWith('<p>About birds</p>'));
spy_log.reset();
// make an ajax request on /birds/about
app.httpGet('/birds/about', (req, res) => {
assert(spy_log.callCount === 2);
assert(spy_log.withArgs('<p>About birds</p>').calledOnce);
assert(spy_log.neverCalledWith('<p>Birds home page</p>'));
done();
});
});
});
});

View File

@ -1,400 +1,400 @@
/*eslint-env mocha*/
/*global global*/
import {assert} from 'chai';
import sinon from 'sinon';
import FakeXMLHttpRequest from 'fake-xml-http-request';
import Requester, {HTTP_METHODS} from '../lib/requester';
describe('HTTP_METHODS', () => {
it('GET method simple uri', () => {
const uriFn = HTTP_METHODS['GET'].uri;
const dataFn = HTTP_METHODS['GET'].data;
assert(uriFn({uri: '/route', data:{a:'b', c:'d'}}) === '/route?a=b&c=d');
assert(dataFn({uri: '/route', data:{a:'b', c:'d'}}) === undefined);
});
it('GET method uri with query string', () => {
const uriFn = HTTP_METHODS['GET'].uri;
const dataFn = HTTP_METHODS['GET'].data;
assert(uriFn({uri: '/route?x=y&z=a', data:{a:'b', c:'d'}}) === '/route?x=y&z=a&a=b&c=d');
assert(dataFn({uri: '/route?x=y&z=a', data:{a:'b', c:'d'}}) === undefined);
});
it('GET method uri with query string and anchor', () => {
const uriFn = HTTP_METHODS['GET'].uri;
const dataFn = HTTP_METHODS['GET'].data;
assert(uriFn({uri: '/route?x=y&z=a#anchor1', data:{a:'b', c:'d'}}) === '/route?x=y&z=a&a=b&c=d#anchor1');
assert(dataFn({uri: '/route?x=y&z=a#anchor1', data:{a:'b', c:'d'}}) === undefined);
});
});
describe('Requester', () => {
function xHttpWillRespond(xhttp, readyState, status, statusText, responseText) {
const stub_send = sinon.stub(xhttp, 'send', function() {
this.readyState = readyState;
this.status = status;
this.statusText = statusText;
this.responseText = responseText;
this.onreadystatechange()
});
const stub_open = sinon.stub(xhttp, 'open', function() {});
const stub_setRequestHeader = sinon.stub(xhttp, 'setRequestHeader', function() {});
return {stub_open, stub_send, stub_setRequestHeader};
}
function xHttpWillThrow(xhttp, sendErrorName, openErrorName) {
const stub_send = sinon.stub(xhttp, 'send', function() {
if (sendErrorName) {
throw {name: sendErrorName};
}
});
const stub_open = sinon.stub(xhttp, 'open', function() {
if (openErrorName) {
throw {name: openErrorName};
}
});
return {stub_open, stub_send};
}
let xhttp;
beforeEach(() => {
// Stub XMLHttpRequest
xhttp = new FakeXMLHttpRequest();
global.XMLHttpRequest = () => {
return xhttp;
};
});
describe('GET Requests', () => {
it('with data', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
requester.fetch({method: 'GET', uri:'/route1', data:{p1: 'a', p2: 'b', p3: 'c'}},
(request, response) => {
assert(request.uri === '/route1?p1=a&p2=b&p3=c');
done();
},
(err) => {
done(err);
});
});
it('without data', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
requester.fetch({method: 'GET', uri:'/route1'}, (request, response) => {
assert(stub_open.calledOnce);
assert(stub_send.calledOnce);
assert(stub_open.calledBefore(stub_send));
assert(request.uri === '/route1');
assert(request.method === 'GET');
assert(request.data === undefined);
assert(response.status === 200);
assert(response.statusText === 'OK');
assert(response.responseText === '<p>content!</p>');
assert(response.errorThrown === undefined);
assert(response.errors === undefined);
done();
},
(error) => {
done(error);
});
});
});
describe('POST Requests', () => {
it('with data', (done) => {
const requester = new Requester();
const {stub_open, stub_send, stub_setRequestHeader} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
requester.fetch({method: 'POST', uri:'/route1', data:{p1: 'a', p2: 'b', p3: 'c'}},
(request, response) => {
assert(stub_open.calledOnce);
assert(stub_setRequestHeader.calledOnce);
assert(stub_send.calledOnce);
assert(stub_open.calledBefore(stub_send));
assert(stub_setRequestHeader.calledAfter(stub_open));
assert(stub_setRequestHeader.calledBefore(stub_send));
assert(request.uri === '/route1');
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
assert(request.data === 'p1=a&p2=b&p3=c');
done();
},
(err) => {
done(err);
});
});
it('without data', (done) => {
const requester = new Requester();
const {stub_open, stub_send, stub_setRequestHeader} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
requester.fetch({method: 'POST', uri:'/route1'},
(request, response) => {
assert(stub_open.calledOnce);
assert(stub_setRequestHeader.calledOnce);
assert(stub_send.calledOnce);
assert(stub_open.calledBefore(stub_send));
assert(stub_setRequestHeader.calledAfter(stub_open));
assert(stub_setRequestHeader.calledBefore(stub_send));
assert(request.uri === '/route1');
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
assert(request.data === undefined);
done();
},
(err) => {
done(err);
});
});
it('with custom headers', (done) => {
const requester = new Requester();
const {stub_open, stub_send, stub_setRequestHeader} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
requester.fetch({method: 'POST', uri:'/route1', headers: {'Accept-Charset': 'utf-8'}},
(request, response) => {
assert(stub_open.calledOnce);
assert(stub_setRequestHeader.calledTwice);
assert(stub_send.calledOnce);
assert(stub_open.calledBefore(stub_send));
assert(stub_setRequestHeader.calledAfter(stub_open));
assert(stub_setRequestHeader.calledBefore(stub_send));
assert(request.uri === '/route1');
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
assert(request.headers['Accept-Charset'] === 'utf-8');
assert(request.data === undefined);
done();
},
(err) => {
done(err);
});
});
});
describe('HTTP errors', () => {
it('request returns no network', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 0);
requester.fetch({method: 'GET', uri: '/route1'}, null,
(request, response) => {
assert(stub_open.calledOnce);
assert(stub_send.calledOnce);
assert(stub_open.calledBefore(stub_send));
assert(response.status === 0);
assert(response.statusText === undefined);
assert(response.responseText === undefined);
assert(response.errorThrown === undefined);
assert(response.errors.length !== 0);
done();
});
});
it('request returns 401', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 401, 'not authenticated', '');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === 401);
assert(response.statusText === 'not authenticated');
assert(response.responseText === undefined);
assert(response.errorThrown === undefined);
assert(response.errors.length !== 0);
done();
});
});
it('request returns 404', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 404, 'page not found', '');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === 404);
assert(response.statusText === 'page not found');
assert(response.responseText === undefined);
assert(response.errorThrown === undefined);
assert(response.errors.length !== 0);
done();
});
});
it('request returns 500', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 500, 'server error', '');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === 500);
assert(response.statusText === 'server error');
assert(response.responseText === undefined);
assert(response.errorThrown === undefined);
assert(response.errors.length !== 0);
done();
});
});
it('request returns 501 (http status not managed)', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 501, 'Not Implemented', '');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === 501);
assert(response.statusText === 'Not Implemented');
assert(response.responseText === undefined);
assert(response.errorThrown === undefined);
assert(response.errors.length !== 0);
done();
});
});
it('request returns syntax error', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'SyntaxError');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === undefined);
assert(response.statusText === undefined);
assert(response.responseText === undefined);
assert(response.errorThrown.name === 'SyntaxError');
assert(response.errors.length !== 0);
done();
});
});
it('request returns timeout error', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'TimeoutError');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === undefined);
assert(response.statusText === undefined);
assert(response.responseText === undefined);
assert(response.errorThrown.name === 'TimeoutError');
assert(response.errors.length !== 0);
done();
});
});
it('request returns abort error', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'AbortError');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === undefined);
assert(response.statusText === undefined);
assert(response.responseText === undefined);
assert(response.errorThrown.name === 'AbortError');
assert(response.errors.length !== 0);
done();
});
});
it('request returns network error', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'NetworkError');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === undefined);
assert(response.statusText === undefined);
assert(response.responseText === undefined);
assert(response.errorThrown.name === 'NetworkError');
assert(response.errors.length !== 0);
done();
});
});
it('request returns unknown error', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'BlaBlaError');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === undefined);
assert(response.statusText === undefined);
assert(response.responseText === undefined);
assert(response.errorThrown.name === 'BlaBlaError');
assert(response.errors.length !== 0);
done();
});
});
});
});
/*eslint-env mocha*/
/*global global*/
import {assert} from 'chai';
import sinon from 'sinon';
import FakeXMLHttpRequest from 'fake-xml-http-request';
import Requester, {HTTP_METHODS} from '../lib/requester';
describe('HTTP_METHODS', () => {
it('GET method simple uri', () => {
const uriFn = HTTP_METHODS['GET'].uri;
const dataFn = HTTP_METHODS['GET'].data;
assert(uriFn({uri: '/route', data:{a:'b', c:'d'}}) === '/route?a=b&c=d');
assert(dataFn({uri: '/route', data:{a:'b', c:'d'}}) === undefined);
});
it('GET method uri with query string', () => {
const uriFn = HTTP_METHODS['GET'].uri;
const dataFn = HTTP_METHODS['GET'].data;
assert(uriFn({uri: '/route?x=y&z=a', data:{a:'b', c:'d'}}) === '/route?x=y&z=a&a=b&c=d');
assert(dataFn({uri: '/route?x=y&z=a', data:{a:'b', c:'d'}}) === undefined);
});
it('GET method uri with query string and anchor', () => {
const uriFn = HTTP_METHODS['GET'].uri;
const dataFn = HTTP_METHODS['GET'].data;
assert(uriFn({uri: '/route?x=y&z=a#anchor1', data:{a:'b', c:'d'}}) === '/route?x=y&z=a&a=b&c=d#anchor1');
assert(dataFn({uri: '/route?x=y&z=a#anchor1', data:{a:'b', c:'d'}}) === undefined);
});
});
describe('Requester', () => {
function xHttpWillRespond(xhttp, readyState, status, statusText, responseText) {
const stub_send = sinon.stub(xhttp, 'send', function() {
this.readyState = readyState;
this.status = status;
this.statusText = statusText;
this.responseText = responseText;
this.onreadystatechange();
});
const stub_open = sinon.stub(xhttp, 'open', function() {});
const stub_setRequestHeader = sinon.stub(xhttp, 'setRequestHeader', function() {});
return {stub_open, stub_send, stub_setRequestHeader};
}
function xHttpWillThrow(xhttp, sendErrorName, openErrorName) {
const stub_send = sinon.stub(xhttp, 'send', function() {
if (sendErrorName) {
throw {name: sendErrorName};
}
});
const stub_open = sinon.stub(xhttp, 'open', function() {
if (openErrorName) {
throw {name: openErrorName};
}
});
return {stub_open, stub_send};
}
let xhttp;
beforeEach(() => {
// Stub XMLHttpRequest
xhttp = new FakeXMLHttpRequest();
global.XMLHttpRequest = () => {
return xhttp;
};
});
describe('GET Requests', () => {
it('with data', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
requester.fetch({method: 'GET', uri:'/route1', data:{p1: 'a', p2: 'b', p3: 'c'}},
(request, response) => {
assert(request.uri === '/route1?p1=a&p2=b&p3=c');
done();
},
(err) => {
done(err);
});
});
it('without data', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
requester.fetch({method: 'GET', uri:'/route1'}, (request, response) => {
assert(stub_open.calledOnce);
assert(stub_send.calledOnce);
assert(stub_open.calledBefore(stub_send));
assert(request.uri === '/route1');
assert(request.method === 'GET');
assert(request.data === undefined);
assert(response.status === 200);
assert(response.statusText === 'OK');
assert(response.responseText === '<p>content!</p>');
assert(response.errorThrown === undefined);
assert(response.errors === undefined);
done();
},
(error) => {
done(error);
});
});
});
describe('POST Requests', () => {
it('with data', (done) => {
const requester = new Requester();
const {stub_open, stub_send, stub_setRequestHeader} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
requester.fetch({method: 'POST', uri:'/route1', data:{p1: 'a', p2: 'b', p3: 'c'}},
(request, response) => {
assert(stub_open.calledOnce);
assert(stub_setRequestHeader.calledOnce);
assert(stub_send.calledOnce);
assert(stub_open.calledBefore(stub_send));
assert(stub_setRequestHeader.calledAfter(stub_open));
assert(stub_setRequestHeader.calledBefore(stub_send));
assert(request.uri === '/route1');
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
assert(request.data === 'p1=a&p2=b&p3=c');
done();
},
(err) => {
done(err);
});
});
it('without data', (done) => {
const requester = new Requester();
const {stub_open, stub_send, stub_setRequestHeader} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
requester.fetch({method: 'POST', uri:'/route1'},
(request, response) => {
assert(stub_open.calledOnce);
assert(stub_setRequestHeader.calledOnce);
assert(stub_send.calledOnce);
assert(stub_open.calledBefore(stub_send));
assert(stub_setRequestHeader.calledAfter(stub_open));
assert(stub_setRequestHeader.calledBefore(stub_send));
assert(request.uri === '/route1');
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
assert(request.data === undefined);
done();
},
(err) => {
done(err);
});
});
it('with custom headers', (done) => {
const requester = new Requester();
const {stub_open, stub_send, stub_setRequestHeader} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
requester.fetch({method: 'POST', uri:'/route1', headers: {'Accept-Charset': 'utf-8'}},
(request, response) => {
assert(stub_open.calledOnce);
assert(stub_setRequestHeader.calledTwice);
assert(stub_send.calledOnce);
assert(stub_open.calledBefore(stub_send));
assert(stub_setRequestHeader.calledAfter(stub_open));
assert(stub_setRequestHeader.calledBefore(stub_send));
assert(request.uri === '/route1');
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
assert(request.headers['Accept-Charset'] === 'utf-8');
assert(request.data === undefined);
done();
},
(err) => {
done(err);
});
});
});
describe('HTTP errors', () => {
it('request returns no network', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 0);
requester.fetch({method: 'GET', uri: '/route1'}, null,
(request, response) => {
assert(stub_open.calledOnce);
assert(stub_send.calledOnce);
assert(stub_open.calledBefore(stub_send));
assert(response.status === 0);
assert(response.statusText === undefined);
assert(response.responseText === undefined);
assert(response.errorThrown === undefined);
assert(response.errors.length !== 0);
done();
});
});
it('request returns 401', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 401, 'not authenticated', '');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === 401);
assert(response.statusText === 'not authenticated');
assert(response.responseText === undefined);
assert(response.errorThrown === undefined);
assert(response.errors.length !== 0);
done();
});
});
it('request returns 404', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 404, 'page not found', '');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === 404);
assert(response.statusText === 'page not found');
assert(response.responseText === undefined);
assert(response.errorThrown === undefined);
assert(response.errors.length !== 0);
done();
});
});
it('request returns 500', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 500, 'server error', '');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === 500);
assert(response.statusText === 'server error');
assert(response.responseText === undefined);
assert(response.errorThrown === undefined);
assert(response.errors.length !== 0);
done();
});
});
it('request returns 501 (http status not managed)', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 501, 'Not Implemented', '');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === 501);
assert(response.statusText === 'Not Implemented');
assert(response.responseText === undefined);
assert(response.errorThrown === undefined);
assert(response.errors.length !== 0);
done();
});
});
it('request returns syntax error', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'SyntaxError');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === undefined);
assert(response.statusText === undefined);
assert(response.responseText === undefined);
assert(response.errorThrown.name === 'SyntaxError');
assert(response.errors.length !== 0);
done();
});
});
it('request returns timeout error', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'TimeoutError');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === undefined);
assert(response.statusText === undefined);
assert(response.responseText === undefined);
assert(response.errorThrown.name === 'TimeoutError');
assert(response.errors.length !== 0);
done();
});
});
it('request returns abort error', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'AbortError');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === undefined);
assert(response.statusText === undefined);
assert(response.responseText === undefined);
assert(response.errorThrown.name === 'AbortError');
assert(response.errors.length !== 0);
done();
});
});
it('request returns network error', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'NetworkError');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === undefined);
assert(response.statusText === undefined);
assert(response.responseText === undefined);
assert(response.errorThrown.name === 'NetworkError');
assert(response.errors.length !== 0);
done();
});
});
it('request returns unknown error', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'BlaBlaError');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
assert(response.status === undefined);
assert(response.statusText === undefined);
assert(response.responseText === undefined);
assert(response.errorThrown.name === 'BlaBlaError');
assert(response.errors.length !== 0);
done();
});
});
});
});

View File

@ -163,7 +163,7 @@ describe('Router', () => {
let r = router.routes('/route1/subroute?a=b&c=d', 'GET');
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
assert(r[0].data === undefined)
assert(r[0].data === undefined);
});
it('route with anchor', () => {
@ -174,7 +174,7 @@ describe('Router', () => {
let r = router.routes('/route1/subroute#a=b&c=d', 'GET');
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
assert(r[0].data === undefined)
assert(r[0].data === undefined);
});
it('route with query string and anchor', () => {
@ -185,7 +185,7 @@ describe('Router', () => {
let r = router.routes('/route1/subroute?a=b&c=d#anchor1', 'GET');
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
assert(r[0].data === undefined)
assert(r[0].data === undefined);
});
});