added application::listen method - fixed unit test with exceptions - removed unecessary packages

This commit is contained in:
Camel Aissani 2016-07-08 23:29:40 +02:00
parent 1c4547b2f1
commit 401135e124
7 changed files with 265 additions and 172 deletions

View File

@ -33,7 +33,7 @@ export default class Application {
use(...args) {
if (args.length === 0) {
throw new TypeError(`use method takes at least a middleware or a router`);
throw new TypeError(`use method takes at least a middleware or a router`);
}
let baseUri, middleware, router, which;
@ -45,7 +45,7 @@ export default class Application {
}
if (!(which instanceof Middleware) && (typeof which !== 'function') && !(which instanceof Router)) {
throw new TypeError(`use method takes at least a middleware or a router`);
throw new TypeError(`use method takes at least a middleware or a router`);
}
if (which instanceof Router) {
@ -61,9 +61,76 @@ export default class Application {
this.routers.push(router);
}
///////////////////////////////////////////////////////
// Ajax request
_fetch({method, uri, headers, data}, resolve, reject) {
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._callMiddlewareEntered(currentRoutes, request)
} else if (document.readyState === 'interactive') {
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();
@ -74,71 +141,52 @@ export default class Application {
}
}
}
}
_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 = [];
for (const router of this.routers) {
const routes = router.routes(uri, method);
currentRoutes.push(...routes);
}
const currentRoutes = this._routes(uri, method);
// calls middleware entered method
for (const route of currentRoutes) {
if (route.middleware.entered) {
route.middleware.entered({method, uri, headers, data});
}
if (route.middleware.next && !route.middleware.next()) {
break;
}
}
this._callMiddlewareEntered(currentRoutes, {method, uri, headers, data});
// invokes http request
this.requester.fetch({uri, method},
(request, response) => {
for (const route of currentRoutes) {
route.visited = {method, uri, headers, data};
// 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;
}
}
}
this._callMiddlewareUpdated(currentRoutes, request, response);
if (resolve) {
resolve(request, response);
}
},
(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;
}
}
}
this._callMiddlewareFailed(currentRoutes, request, response);
if (reject) {
reject(request, response);
}
@ -151,7 +199,7 @@ Object.keys(HTTP_METHODS).reduce((reqProto, method) => {
const middlewareMethodeName = method.toLowerCase();
reqProto[middlewareMethodeName] = function(...args) {
if (args.length === 0) {
throw new TypeError(`${middlewareMethodeName} method takes at least a middleware`);
throw new TypeError(`${middlewareMethodeName} method takes at least a middleware`);
}
let baseUri, middleware, which;
@ -163,7 +211,7 @@ Object.keys(HTTP_METHODS).reduce((reqProto, method) => {
}
if (!(which instanceof Middleware) && (typeof which !== 'function')) {
throw new TypeError(`${middlewareMethodeName} method takes at least a middleware`);
throw new TypeError(`${middlewareMethodeName} method takes at least a middleware`);
}
const router = new Router();
@ -189,4 +237,4 @@ Object.keys(HTTP_METHODS).reduce((reqProto, method) => {
}
return reqProto;
}, Application.prototype);
}, Application.prototype);

View File

@ -6,7 +6,7 @@ function frontexpress() {
return new Application();
}
frontexpress.Router = Router;
frontexpress.Middleware = Middleware;
frontexpress.Router = (baseUri) => new Router(baseUri);
frontexpress.Middleware = (name) => new Middleware(name);
export default frontexpress;

View File

@ -34,11 +34,30 @@ class Route {
export default class Router {
constructor(baseUri) {
if (baseUri) {
this.baseUri = baseUri;
this._baseUri = baseUri;
}
this._routes = [];
}
set baseUri(uri) {
if (!this._baseUri) {
this._baseUri = uri;
return;
}
if (this._baseUri instanceof RegExp) {
throw new TypeError(`the router already contains a regexp uri ${this._baseUri.toString()} It cannot be mixed with ${uri.toString()}`);
}
if (uri instanceof RegExp) {
throw new TypeError(`the router already contains an uri ${this._baseUri.toString()} It cannot be mixed with regexp ${uri.toString()}`);
}
}
get baseUri() {
return this._baseUri;
}
_add(route) {
this._routes.push(route);
return this;
@ -123,7 +142,7 @@ for (const method of Object.keys(HTTP_METHODS)) {
throw new TypeError(`use ${methodName} method takes at least a middleware`);
}
if (uri && this.baseUri && this.baseUri instanceof RegExp) {
if (uri && this._baseUri && this._baseUri instanceof RegExp) {
throw new TypeError(`router contains a regexp cannot mix with route uri/regexp`);
}

View File

@ -39,10 +39,7 @@
"eslint-loader": "^1.3.0",
"fake-xml-http-request": "^1.4.0",
"istanbul": "^0.4.3",
"jsdom": "^9.2.0",
"mocha": "^2.5.3",
"mocha-jsdom": "^1.1.0",
"rewire": "^2.5.1",
"rimraf": "^2.5.2",
"sinon": "^1.17.4"
}

View File

@ -1,6 +1,7 @@
/*eslint-env mocha*/
import {assert} from 'chai';
import sinon from 'sinon';
import chai from 'chai';
import frontexpress from '../lib/frontexpress';
import Requester from '../lib/requester';
@ -25,14 +26,87 @@ describe('Application', () => {
});
});
describe('listen method', () => {
let eventFn = {};
beforeEach(() => {
global.document = {};
global.window = {
addEventListener(eventType, callback) {
eventFn[eventType] = callback;
},
location: {
pathname: '/route1',
search: '?a=b'
}
}
});
it('with function middleware readyState===interactive', (done) => {
const app = frontexpress();
app.use('/route1', (request, response, next) => {
done();
});
app.listen();
//simulate readystatechange
document.readyState = 'interactive';
document.onreadystatechange();
});
it('with middleware object readyState===loading', (done) => {
const app = frontexpress();
const m = new frontexpress.Middleware();
sinon.stub(m, 'entered', () => {
done();
});
app.use('/route1', m);
app.listen();
//simulate readystatechange
document.readyState = 'loading';
document.onreadystatechange();
});
it('with middleware object readyState===interactive', (done) => {
const app = frontexpress();
const m = new frontexpress.Middleware();
sinon.stub(m, 'updated', () => {
done();
});
app.use('/route1', m);
app.listen();
//simulate readystatechange
document.readyState = 'interactive';
document.onreadystatechange();
});
it('with middleware object event beforeunload', (done) => {
const app = frontexpress();
const m = new frontexpress.Middleware();
sinon.stub(m, 'exited', () => {
done();
});
app.use('/route1', m);
app.listen(() => {
//simulate beforeunload
eventFn['beforeunload']();
});
//simulate readystatechange
document.readyState = 'interactive';
document.onreadystatechange();
});
});
describe('set method', () => {
it('unsupported setting', () => {
const app = frontexpress();
try {
app.set('blabla', 'value');
} catch (ex) {
assert(ex instanceof ReferenceError);
}
chai.expect(() => app.set('blabla', 'value')).to.throw(ReferenceError);
});
it('supported setting', () => {
@ -63,12 +137,19 @@ describe('Application', () => {
it('bad arguments', () => {
const app = frontexpress();
app.set('http-requester', requester);
try {
app.use('eee');
} catch (ex) {
assert(ex instanceof TypeError);
}
chai.expect(() => app.use('eee')).to.throw(TypeError);
});
it('mixing uri and regexp', () => {
let router = frontexpress.Router('/subroute');
let app = frontexpress();
app.set('http-requester', requester);
chai.expect(() => app.use(/route/, router)).to.throw(TypeError);
router = frontexpress.Router(/subroute/);
app = frontexpress();
app.set('http-requester', requester);
chai.expect(() => app.use('/route', router)).to.throw(TypeError);
});
it('middleware as function on path /', (done) => {
@ -188,7 +269,7 @@ describe('Application', () => {
it('router on path /', (done) => {
const spy = sinon.spy();
const router = new frontexpress.Router();
const router = frontexpress.Router();
router
.get((request, response, next) => {spy()})
.post((request, response, next) => {spy()});
@ -209,7 +290,7 @@ describe('Application', () => {
it('router on path /route1', (done) => {
const spy = sinon.spy();
const router = new frontexpress.Router();
const router = frontexpress.Router();
router
.get((request, response, next) => {spy()})
.post((request, response, next) => {spy()});
@ -235,7 +316,7 @@ describe('Application', () => {
const app = frontexpress();
app.set('http-requester', requester);
const router = new frontexpress.Router();
const router = frontexpress.Router();
router.get('/subroute1', middleware);
app.use('/route1', router);
@ -270,21 +351,9 @@ describe('Application', () => {
it('bad arguments', () => {
const app = frontexpress();
app.set('http-requester', requester);
try {
app.get('eee');
} catch (ex) {
assert(ex instanceof TypeError);
}
try {
app.get(new frontexpress.Router());
} catch (ex) {
assert(ex instanceof TypeError);
}
try {
app.get('/route1', new frontexpress.Router());
} catch (ex) {
assert(ex instanceof TypeError);
}
chai.expect(() => app.get('eee')).to.throw(TypeError);
chai.expect(() => app.get(frontexpress.Router())).to.throw(TypeError);
chai.expect(() => app.get('/route1', frontexpress.Router())).to.throw(TypeError);
});
it('middleware as function on path /', (done) => {

View File

@ -2,7 +2,6 @@
/*global global*/
import {assert} from 'chai';
import sinon from 'sinon';
import jsdom from 'mocha-jsdom';
import FakeXMLHttpRequest from 'fake-xml-http-request';
import Requester, {HTTP_METHODS} from '../lib/requester';
@ -67,19 +66,6 @@ describe('Requester', () => {
let xhttp;
// Init DOM with a fake document
// <base> and uri (initial uri) allow to do pushState in jsdom
jsdom({
html:`
<html>
<head>
<base href="http://localhost:8080/"></base>
</head>
</html>
`,
url: 'http://localhost:8080/'
});
beforeEach(() => {
// Stub XMLHttpRequest
xhttp = new FakeXMLHttpRequest();

View File

@ -1,6 +1,7 @@
/*eslint-env mocha*/
import {assert} from 'chai';
import sinon from 'sinon';
import chai from 'chai';
import frontexpress from '../lib/frontexpress';
import {HTTP_METHODS} from '../lib/requester';
@ -8,17 +9,18 @@ describe('Router', () => {
describe('generated methods', () => {
it('checks http methods are exposed', ()=> {
assert(typeof frontexpress.Router.prototype.all === 'function');
assert(typeof frontexpress.Router.prototype.get === 'function');
assert(typeof frontexpress.Router.prototype.put === 'function');
assert(typeof frontexpress.Router.prototype.post === 'function');
assert(typeof frontexpress.Router.prototype.delete === 'function');
const router = frontexpress.Router();
assert(typeof router.all === 'function');
assert(typeof router.get === 'function');
assert(typeof router.put === 'function');
assert(typeof router.post === 'function');
assert(typeof router.delete === 'function');
});
});
describe('visited method', () => {
it('get visited route', ()=> {
const router = new frontexpress.Router();
const router = frontexpress.Router();
const middleware = (request, response) => {};
router.get(middleware);
@ -30,7 +32,7 @@ describe('Router', () => {
describe('routes method', () => {
it('no root path and no path uri', ()=> {
const router = new frontexpress.Router();
const router = frontexpress.Router();
const middleware = (request, response) => {};
router.get(middleware);
@ -43,7 +45,7 @@ describe('Router', () => {
});
it('no root path and path /routeX', ()=> {
const router = new frontexpress.Router();
const router = frontexpress.Router();
const middleware1 = (request, response) => {};
const middleware2 = (request, response) => {};
const middleware3 = (request, response) => {};
@ -91,7 +93,7 @@ describe('Router', () => {
});
it('no root path and regexp uri', ()=> {
const router = new frontexpress.Router();
const router = frontexpress.Router();
const middleware = new frontexpress.Middleware();
router.get(/^\/route1/, middleware);
@ -105,7 +107,7 @@ describe('Router', () => {
});
it('with root path /route1 and path /subroute', () => {
const router = new frontexpress.Router('/route1');
const router = frontexpress.Router('/route1');
router.get('/subroute', new frontexpress.Middleware());
@ -115,7 +117,7 @@ describe('Router', () => {
});
it('with root path /route1 and no path uri', () => {
const router = new frontexpress.Router('/route1');
const router = frontexpress.Router('/route1');
router.get(new frontexpress.Middleware());
@ -125,7 +127,7 @@ describe('Router', () => {
});
it('duplicate / in route', () => {
const router = new frontexpress.Router('/route1/');
const router = frontexpress.Router('/route1/');
router.get('/subroute', new frontexpress.Middleware());
@ -135,7 +137,7 @@ describe('Router', () => {
});
it('spaces in route', () => {
let router = new frontexpress.Router(' /route1 ');
let router = frontexpress.Router(' /route1 ');
router.get('/subroute ', new frontexpress.Middleware());
@ -145,7 +147,7 @@ describe('Router', () => {
// ----
router = new frontexpress.Router(' /route1 ');
router = frontexpress.Router(' /route1 ');
router.get(new frontexpress.Middleware());
@ -155,7 +157,7 @@ describe('Router', () => {
});
it('route with query string', () => {
let router = new frontexpress.Router('/route1 ');
let router = frontexpress.Router('/route1 ');
router.get('/subroute', new frontexpress.Middleware());
@ -166,7 +168,7 @@ describe('Router', () => {
});
it('route with anchor', () => {
let router = new frontexpress.Router('/route1 ');
let router = frontexpress.Router('/route1 ');
router.get('/subroute', new frontexpress.Middleware());
@ -177,7 +179,7 @@ describe('Router', () => {
});
it('route with query string and anchor', () => {
let router = new frontexpress.Router('/route1 ');
let router = frontexpress.Router('/route1 ');
router.get('/subroute', new frontexpress.Middleware());
@ -190,31 +192,19 @@ describe('Router', () => {
describe('all method', () => {
it('no arguments', () => {
const router = new frontexpress.Router('/route1');
const router = frontexpress.Router('/route1');
assert.throws(router.all, TypeError);
});
it('bad argument', () => {
const router = new frontexpress.Router('/route1');
try {
router.all('ddd');
} catch(ex) {
assert(ex instanceof TypeError);
}
try {
router.all('ddd', 'eee');
} catch(ex) {
assert(ex instanceof TypeError);
}
try {
router.all('ddd', new frontexpress.Router());
} catch(ex) {
assert(ex instanceof TypeError);
}
const router = frontexpress.Router('/route1');
chai.expect(() => router.all('ddd')).to.throw(TypeError);
chai.expect(() => router.all('ddd', 'eee')).to.throw(TypeError);
chai.expect(() => router.all('ddd', frontexpress.Router())).to.throw(TypeError);
});
it('only middleware as argument', () => {
const router = new frontexpress.Router('/route1');
const router = frontexpress.Router('/route1');
const middleware = new frontexpress.Middleware();
const spied_methods = [];
@ -230,7 +220,7 @@ describe('Router', () => {
});
it('with path /route1 and middleware as arguments', () => {
const router = new frontexpress.Router();
const router = frontexpress.Router();
const middleware = new frontexpress.Middleware();
const spied_methods = [];
@ -246,33 +236,21 @@ describe('Router', () => {
});
});
describe('one http (get) method', () => {
describe('one http method (get)', () => {
it('no arguments', () => {
const router = new frontexpress.Router('/route1');
const router = frontexpress.Router('/route1');
assert.throws(router.get, TypeError);
});
it('bad argument', () => {
const router = new frontexpress.Router('/route1');
try {
router.get('ddd');
} catch(ex) {
assert(ex instanceof TypeError);
}
try {
router.get('ddd', 'eee');
} catch(ex) {
assert(ex instanceof TypeError);
}
try {
router.get('ddd', new frontexpress.Router());
} catch(ex) {
assert(ex instanceof TypeError);
}
const router = frontexpress.Router('/route1');
chai.expect(() => router.get('ddd')).to.throw(TypeError);
chai.expect(() => router.get('ddd', 'eee')).to.throw(TypeError);
chai.expect(() => router.get('ddd', frontexpress.Router())).to.throw(TypeError);
});
it('only middleware as argument', () => {
const router = new frontexpress.Router('/');
const router = frontexpress.Router('/');
const middleware = new frontexpress.Middleware();
router.get(middleware);
@ -285,7 +263,7 @@ describe('Router', () => {
});
it('with path /route1 and middleware as arguments', () => {
const router = new frontexpress.Router();
const router = frontexpress.Router();
const middleware = new frontexpress.Middleware();
router.get('/route1', middleware);
@ -298,17 +276,13 @@ describe('Router', () => {
});
it('router with regexp and route with /route1', () => {
const router = new frontexpress.Router(/^\//);
const router = frontexpress.Router(/^\//);
const middleware = new frontexpress.Middleware();
try {
router.get('/route1', middleware);
} catch(ex) {
assert(ex instanceof TypeError)
}
chai.expect(() => router.get('/route1', middleware)).to.throw(TypeError);
});
it('router with regexp and route without uri', () => {
const router = new frontexpress.Router(/^\/part/);
const router = frontexpress.Router(/^\/part/);
const middleware = new frontexpress.Middleware();
router.get(middleware);