diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e86ef84 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +coverage +dist \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index 60028c9..2e57f7e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -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 + } } \ No newline at end of file diff --git a/README.md b/README.md index e869820..78499a4 100644 --- a/README.md +++ b/README.md @@ -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 = '
Birds home page
'; -}); +}); -// react on about route -router.get('/about', (req, res) => { - document.querySelector('.content').innerHTML = 'About birds
'; -}); +// react on about route +router.get('/about', (req, res) => { + document.querySelector('.content').innerHTML = 'About birds
'; +}); export default router; ``` diff --git a/lib/application.js b/lib/application.js index c232c19..890a897 100755 --- a/lib/application.js +++ b/lib/application.js @@ -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); \ No newline at end of file diff --git a/lib/middleware.js b/lib/middleware.js index 1792827..d67bb3e 100755 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -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; + } +} diff --git a/lib/requester.js b/lib/requester.js index 014a679..fc20184 100755 --- a/lib/requester.js +++ b/lib/requester.js @@ -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(); } diff --git a/lib/router.js b/lib/router.js index c2f0269..5745556 100755 --- a/lib/router.js +++ b/lib/router.js @@ -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; - } + }; } diff --git a/test/application-test.js b/test/application-test.js index e0b2222..014bfc7 100755 --- a/test/application-test.js +++ b/test/application-test.js @@ -1,773 +1,772 @@ -/*eslint-env mocha*/ -import chai, {assert} from 'chai'; -import sinon from 'sinon'; -import frontexpress from '../lib/frontexpress'; -import Requester from '../lib/requester'; - -describe('Application', () => { - let requester; - - describe('generated methods', () => { - it('checks http methods are exposed', ()=> { - const app = frontexpress(); - assert(typeof app.httpGet === 'function'); - assert(typeof app.httpPut === 'function'); - assert(typeof app.httpPost === 'function'); - assert(typeof app.httpDelete === 'function'); - }); - - it('checks middleware methods are exposed', ()=> { - const app = frontexpress(); - assert(typeof app.get === 'function'); - assert(typeof app.put === 'function'); - assert(typeof app.post === 'function'); - assert(typeof app.delete === 'function'); - }); - }); - - 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 = 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 = 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 = 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(); - chai.expect(() => app.set('blabla', 'value')).to.throw(ReferenceError); - }); - - it('supported setting', () => { - const requester = new Requester(); - const app = frontexpress(); - app.set('http-requester', requester); - assert(app.requester === requester); - }); - }); - - describe('use method', () => { - beforeEach(()=>{ - requester = new Requester(); - sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { - resolve( - {uri, method, headers, data}, - {status: 200, statusText: 'OK', responseText:''} - ); - }); - }); - - it('no arguments', () => { - const app = frontexpress(); - app.set('http-requester', requester); - assert.throws(app.use, TypeError); - }); - - it('bad arguments', () => { - const app = frontexpress(); - app.set('http-requester', requester); - 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) => { - const spy = sinon.spy(); - - const app = frontexpress(); - app.set('http-requester', requester); - app.use((request, response, next) => {spy()}); - - app.httpGet('/', (request, response) => { - assert(spy.callCount === 1); - - app.httpPost('/route1', (request, response) => { - assert(spy.callCount === 2); - done(); - }); - }); - }); - - it('middleware as function on path /route1', (done) => { - const spy = sinon.spy(); - - const app = frontexpress(); - app.set('http-requester', requester); - app.use('/route1', (request, response, next) => {spy()}); - - app.httpGet('/route1', (request, response) => { - assert(spy.callCount === 1); - - app.httpPost('/route1', (request, response) => { - assert(spy.callCount === 2); - done(); - }); - }); - }); - - it('middleware as function on path / request fails', (done) => { - requester = new Requester(); - sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { - reject( - {uri, method, headers, data}, - {status: 401, statusText: 'not logged'} - ); - }); - - const spy = sinon.spy(); - - const app = frontexpress(); - app.set('http-requester', requester); - app.use((request, response, next) => {spy()}); - - app.httpGet('/', null, (request, response) => { - assert(spy.callCount === 1); - assert(response.status === 401); - done(); - }); - }); - - it('middleware as object on path /', (done) => { - const middleware = frontexpress.Middleware('on path /'); - const spy = sinon.spy(middleware, 'updated'); - - const app = frontexpress(); - app.set('http-requester', requester); - app.use(middleware); - - app.httpGet('/', (request, response) => { - assert(spy.callCount === 1); - - app.httpPost('/route1', (request, response) => { - assert(spy.callCount === 2); - done(); - }); - }); - }); - - it('middleware as object on path /route1', (done) => { - const middleware = frontexpress.Middleware('on path /route1'); - const spy = sinon.spy(middleware, 'updated'); - - const app = frontexpress(); - app.set('http-requester', requester); - app.use('/route1', middleware); - - app.httpGet('/route1', (request, response) => { - assert(spy.callCount === 1); - - app.httpPost('/route1', (request, response) => { - assert(spy.callCount === 2); - done(); - }); - }); - }); - - it('middleware as object on path / request fails', (done) => { - requester = new Requester(); - sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { - reject( - {uri, method, headers, data}, - {status: 401, statusText: 'not logged'} - ); - }); - - const middleware = frontexpress.Middleware('on path /'); - const spy = sinon.spy(middleware, 'failed'); - - const app = frontexpress(); - app.set('http-requester', requester); - app.use(middleware); - - app.httpGet('/', null, (request, response) => { - assert(spy.callCount === 1); - assert(response.status === 401); - done(); - }); - }); - - it('router on path /', (done) => { - const spy = sinon.spy(); - const router = frontexpress.Router(); - router - .get((request, response, next) => {spy()}) - .post((request, response, next) => {spy()}); - - const app = frontexpress(); - app.set('http-requester', requester); - app.use(router); - - app.httpGet('/', (request, response) => { - assert(spy.callCount === 1); - - app.httpPost('/route1', (request, response) => { - assert(spy.callCount === 2); - done(); - }); - }); - }); - - it('router on path /route1', (done) => { - const spy = sinon.spy(); - const router = frontexpress.Router(); - router - .get((request, response, next) => {spy()}) - .post((request, response, next) => {spy()}); - - const app = frontexpress(); - app.set('http-requester', requester); - app.use('/route1', router); - - app.httpGet('/route1', (request, response) => { - assert(spy.callCount === 1); - - app.httpPost('/route1', (request, response) => { - assert(spy.callCount === 2); - done(); - }); - }); - }); - - it('router with base uri', (done)=> { - const middleware = frontexpress.Middleware('get middleware'); - const spy = sinon.spy(middleware, 'updated'); - - const app = frontexpress(); - app.set('http-requester', requester); - - const router = frontexpress.Router(); - router.get('/subroute1', middleware); - - app.use('/route1', router); - - app.httpGet('/route1/subroute1', (request, response) => { - assert(spy.calledOnce); - done(); - }, - (request, response) => { - done(response); - }); - }); - - it('use two routers', (done)=> { - - const router1 = frontexpress.Router(); - const m1 = frontexpress.Middleware(); - const spy1 = sinon.spy(m1, 'updated'); - router1.get('/subroute1', m1); - - const router2 = frontexpress.Router(); - const m2 = frontexpress.Middleware(); - const spy2 = sinon.spy(m2, 'updated'); - router2.get('/subroute2', m2); - - assert(router1 !== router2); - - - const app = frontexpress(); - app.set('http-requester', requester); - app.use(/^\/route1/, router1); - app.use(/^\/route2/, router2); - - app.httpGet('/route1/subroute1', - (request, response) => { - assert(spy1.calledOnce); - app.httpGet('/route2/subroute2', (request, response) => { - assert(spy2.calledOnce); - done(); - }, - (request, response) => { - done(response); - }); - }, - (request, response) => { - done(response); - }); - - }); - }); - - describe('get method', () => { - beforeEach(()=>{ - requester = new Requester(); - sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { - resolve( - {uri, method, headers, data}, - {status: 200, statusText: 'OK', responseText:''} - ); - }); - }); - - it('no arguments', () => { - const app = frontexpress(); - app.set('http-requester', requester); - assert.throws(app.get, TypeError); - }); - - it('bad arguments', () => { - const app = frontexpress(); - app.set('http-requester', requester); - 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) => { - const spy = sinon.spy(); - - const app = frontexpress(); - app.set('http-requester', requester); - app.get((request, respons, next) => {spy()}); - - app.httpGet('/', (request, response) => { - assert(spy.callCount === 1); - - app.httpPost('/route1', (request, response) => { - assert(spy.callCount === 1); - done(); - }); - }); - }); - - it('middleware as function on path /route1', (done) => { - const spy = sinon.spy(); - - const app = frontexpress(); - app.set('http-requester', requester); - app.get('/route1', (request, response, next) => {spy()}); - - app.httpGet('/route1', (request, response) => { - assert(spy.callCount === 1); - - app.httpPost('/route1', (request, response) => { - assert(spy.callCount === 1); - done(); - }); - }); - }); - - it('middleware as object on path /', (done) => { - const middleware = frontexpress.Middleware('on path /'); - const spy = sinon.spy(middleware, 'updated'); - - const app = frontexpress(); - app.set('http-requester', requester); - app.get(middleware); - - app.httpGet('/', (request, response) => { - assert(spy.callCount === 1); - - app.httpPost('/route1', (request, response) => { - assert(spy.callCount === 1); - done(); - }); - }); - }); - - it('middleware as object on path /route1', (done) => { - const middleware = frontexpress.Middleware('on path /route1'); - const spy = sinon.spy(middleware, 'updated'); - - const app = frontexpress(); - app.set('http-requester', requester); - app.get(middleware); - - app.httpGet('/route1', (request, response) => { - assert(spy.callCount === 1); - - app.httpPost('/route1', (request, response) => { - assert(spy.callCount === 1); - done(); - }); - }); - }); - }); - - describe('route method', () => { - beforeEach(()=>{ - requester = new Requester(); - sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { - resolve( - {uri, method, headers, data}, - {status: 200, statusText: 'OK', responseText:''} - ); - }); - }); - - it('on root path not specified', (done) => { - const spy = sinon.spy(); - - const app = frontexpress(); - app.set('http-requester', requester); - app.route().get((request, response, next) => {spy()}); - - app.httpGet('/', (request, response) => { - assert(spy.calledOnce); - app.httpPost('/', (request, response) => { - assert(spy.calledOnce); - done(); - }); - }); - - }); - - it('on root path /', (done) => { - const spy = sinon.spy(); - - const app = frontexpress(); - app.set('http-requester', requester); - app.route('/').get((request, response, next) => {spy()}); - - app.httpGet('/', (request, response) => { - assert(spy.calledOnce); - app.httpPost('/', (request, response) => { - assert(spy.calledOnce); - done(); - }); - }); - - }); - - it('on root path /route1', (done) => { - const spy = sinon.spy(); - - const app = frontexpress(); - app.set('http-requester', requester); - app.route('/route1').get((request, response, next) => {spy()}); - - app.httpGet('/route1', (request, response) => { - assert(spy.calledOnce); - app.httpPost('/route1', (request, response) => { - assert(spy.calledOnce); - done(); - }); - }); - - }); - - it('on root path undefined with sub path /subroute1', (done) => { - const spy = sinon.spy(); - - const app = frontexpress(); - app.set('http-requester', requester); - app.route().get('/subroute1', (request, response, next) => {spy()}); - - app.httpGet('/subroute1', (request, response) => { - assert(spy.calledOnce); - app.httpPost('/subroute1', (request, response) => { - assert(spy.calledOnce); - done(); - }); - }); - }); - - it('on root path / with sub path /subroute1', (done) => { - const spy = sinon.spy(); - - const app = frontexpress(); - app.set('http-requester', requester); - app.route('/').get('/subroute1', (request, response, next) => {spy()}); - - app.httpGet('/subroute1', (request, response) => { - assert(spy.calledOnce); - app.httpPost('/subroute1', (request, response) => { - assert(spy.calledOnce); - done(); - }); - }); - }); - - it('on root path /route1 with sub path /subroute1', (done) => { - const spy = sinon.spy(); - - const app = frontexpress(); - app.set('http-requester', requester); - app.route('/route1').get('/subroute1', (request, response, next) => {spy()}); - - app.httpGet('/route1/subroute1', (request, response) => { - assert(spy.calledOnce); - app.httpPost('/route1/subroute1', (request, response) => { - assert(spy.calledOnce); - done(); - }); - }); - }); - }); - - describe('middleware object lifecycle', () => { - beforeEach(()=>{ - requester = new Requester(); - sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { - resolve( - {uri, method, headers, data}, - {status: 200, statusText: 'OK', responseText:''} - ); - }); - }); - - it('http GET request', (done) => { - const app = frontexpress(); - app.set('http-requester', requester); - - const getMiddleware = frontexpress.Middleware('get middleware'); - const spy_get_entered = sinon.spy(getMiddleware, 'entered'); - const spy_get_updated = sinon.spy(getMiddleware, 'updated'); - const spy_get_exited = sinon.spy(getMiddleware, 'exited'); - const spy_get_next = sinon.spy(getMiddleware, 'next'); - - app.route('/route1').get(getMiddleware); - - app.httpGet({uri:'/route1', data: {p1: 'a', p2: 'b', p3: 'c'}}, (request, response) => { - assert(spy_get_entered.callCount === 1); - assert(spy_get_updated.callCount === 1); - assert(spy_get_exited.callCount === 0); - assert(spy_get_next.callCount === 2); - - assert(spy_get_entered.calledBefore(spy_get_updated)); - assert(spy_get_next.calledAfter(spy_get_entered)); - assert(spy_get_next.calledAfter(spy_get_updated)); - done(); - }, - (request, response) => { - done(response); - }); - }); - - it('http GET followed by http POST requests', (done) => { - const app = frontexpress(); - app.set('http-requester', requester); - - const allMiddleware = frontexpress.Middleware('all middleware'); - const spy_all_entered = sinon.spy(allMiddleware, 'entered'); - const spy_all_updated = sinon.spy(allMiddleware, 'updated'); - const spy_all_exited = sinon.spy(allMiddleware, 'exited'); - - const getMiddleware = frontexpress.Middleware('get middleware'); - const spy_get_entered = sinon.spy(getMiddleware, 'entered'); - const spy_get_updated = sinon.spy(getMiddleware, 'updated'); - const spy_get_exited = sinon.spy(getMiddleware, 'exited'); - - const postMiddleware = frontexpress.Middleware('post middleware'); - const spy_post_entered = sinon.spy(postMiddleware, 'entered'); - const spy_post_updated = sinon.spy(postMiddleware, 'updated'); - const spy_post_exited = sinon.spy(postMiddleware, 'exited'); - - app.route('/route1') - .all(allMiddleware) - .get(getMiddleware) - .post(postMiddleware); - - app.httpGet('/route1', (request, response) => { - assert(spy_all_entered.callCount === 1); - assert(spy_all_updated.callCount === 1); - assert(spy_all_exited.callCount === 0); - assert(spy_all_entered.calledBefore(spy_get_entered)); - assert(spy_all_entered.calledBefore(spy_post_entered)); - - assert(spy_get_entered.callCount === 1); - assert(spy_get_updated.callCount === 1); - assert(spy_get_exited.callCount === 0); - assert(spy_get_entered.calledBefore(spy_post_entered)); - - assert(spy_post_entered.callCount === 0); - assert(spy_post_updated.callCount === 0); - assert(spy_post_exited.callCount === 0); - - spy_all_entered.reset(); - spy_all_updated.reset(); - spy_all_exited.reset(); - - spy_get_entered.reset(); - spy_get_updated.reset(); - spy_get_exited.reset(); - - spy_post_entered.reset(); - spy_post_updated.reset(); - spy_post_exited.reset(); - - app.httpPost('/route1', (request, response) => { - assert(spy_all_entered.callCount === 1); - assert(spy_all_updated.callCount === 1); - assert(spy_all_exited.callCount === 1); - assert(spy_all_exited.calledBefore(spy_all_entered)); - assert(spy_all_exited.calledBefore(spy_all_updated)); - - assert(spy_get_entered.callCount === 0); - assert(spy_get_updated.callCount === 0); - assert(spy_get_exited.callCount === 1); - - assert(spy_post_entered.callCount === 1); - assert(spy_post_updated.callCount === 1); - assert(spy_post_exited.callCount === 0); - - done(); - }, (request, response) => { - done('should fail'); - }); - }, (request, response) => { - done('should fail'); - }); - }); - - it('request returning error', (done) => { - requester = new Requester(); - sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { - reject( - {uri, method, headers, data}, - {status: 404, statusText: 'page not found'} - ); - }); - const app = frontexpress(); - app.set('http-requester', requester); - - const getMiddleware = frontexpress.Middleware('get middleware'); - const spy_get_failed = sinon.spy(getMiddleware, 'failed'); - - - app.route('/route1').get(getMiddleware); - - app.httpGet('/route1', (request, response) => { - done('should fail'); - }, - (request, response) => { - assert(spy_get_failed.callCount === 1); - done(); - }); - }); - - it('next method', (done) => { - const app = frontexpress(); - app.set('http-requester', requester); - - const m1 = frontexpress.Middleware('m1'); - const m2 = frontexpress.Middleware('m2'); - m2.next = () => false; - const m3 = frontexpress.Middleware('m3'); - - const spy_m1 = sinon.spy(m1, 'updated'); - const spy_m2 = sinon.spy(m2, 'updated'); - const spy_m3 = sinon.spy(m3, 'updated'); - - app.route('/route1').get(m1).get(m2).get(m3); - app.httpGet('/route1', (request, response) => { - assert(spy_m1.calledOnce); - assert(spy_m2.calledOnce); - assert(spy_m3.callCount === 0); - done(); - }); - }); - }); - - describe('middleware as function', () => { - beforeEach(()=>{ - requester = new Requester(); - sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { - resolve( - {uri, method, headers, data}, - {status: 200, statusText: 'OK', responseText:''} - ); - }); - }); - - it('next method', (done) => { - const app = frontexpress(); - app.set('http-requester', requester); - - const m1 = (req, res, next) => {next()}; - const m2 = (req, res, next) => {}; - const m3 = (req, res, next) => {next()}; - - const spy_m1 = sinon.spy(m1); - const spy_m2 = sinon.spy(m2); - const spy_m3 = sinon.spy(m3); - - app.route('/route1').get(spy_m1).get(spy_m2).get(spy_m3); - app.httpGet('/route1', (request, response) => { - assert(spy_m1.calledOnce); - assert(spy_m2.calledOnce); - assert(spy_m3.callCount === 0); - done(); - }); - }); - }); -}); +/*eslint-env mocha*/ +import chai, {assert} from 'chai'; +import sinon from 'sinon'; +import frontexpress from '../lib/frontexpress'; +import Requester from '../lib/requester'; + +describe('Application', () => { + let requester; + + describe('generated methods', () => { + it('checks http methods are exposed', ()=> { + const app = frontexpress(); + assert(typeof app.httpGet === 'function'); + assert(typeof app.httpPut === 'function'); + assert(typeof app.httpPost === 'function'); + assert(typeof app.httpDelete === 'function'); + }); + + it('checks middleware methods are exposed', ()=> { + const app = frontexpress(); + assert(typeof app.get === 'function'); + assert(typeof app.put === 'function'); + assert(typeof app.post === 'function'); + assert(typeof app.delete === 'function'); + }); + }); + + 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 = 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 = 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 = 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(); + chai.expect(() => app.set('blabla', 'value')).to.throw(ReferenceError); + }); + + it('supported setting', () => { + const requester = new Requester(); + const app = frontexpress(); + app.set('http-requester', requester); + assert(app.requester === requester); + }); + }); + + describe('use method', () => { + beforeEach(()=>{ + requester = new Requester(); + sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { + resolve( + {uri, method, headers, data}, + {status: 200, statusText: 'OK', responseText:''} + ); + }); + }); + + it('no arguments', () => { + const app = frontexpress(); + app.set('http-requester', requester); + assert.throws(app.use, TypeError); + }); + + it('bad arguments', () => { + const app = frontexpress(); + app.set('http-requester', requester); + 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) => { + const spy = sinon.spy(); + + const app = frontexpress(); + app.set('http-requester', requester); + app.use((request, response, next) => {spy();}); + + app.httpGet('/', (request, response) => { + assert(spy.callCount === 1); + + app.httpPost('/route1', (request, response) => { + assert(spy.callCount === 2); + done(); + }); + }); + }); + + it('middleware as function on path /route1', (done) => { + const spy = sinon.spy(); + + const app = frontexpress(); + app.set('http-requester', requester); + app.use('/route1', (request, response, next) => {spy();}); + + app.httpGet('/route1', (request, response) => { + assert(spy.callCount === 1); + + app.httpPost('/route1', (request, response) => { + assert(spy.callCount === 2); + done(); + }); + }); + }); + + it('middleware as function on path / request fails', (done) => { + requester = new Requester(); + sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { + reject( + {uri, method, headers, data}, + {status: 401, statusText: 'not logged'} + ); + }); + + const spy = sinon.spy(); + + const app = frontexpress(); + app.set('http-requester', requester); + app.use((request, response, next) => {spy();}); + + app.httpGet('/', null, (request, response) => { + assert(spy.callCount === 1); + assert(response.status === 401); + done(); + }); + }); + + it('middleware as object on path /', (done) => { + const middleware = frontexpress.Middleware('on path /'); + const spy = sinon.spy(middleware, 'updated'); + + const app = frontexpress(); + app.set('http-requester', requester); + app.use(middleware); + + app.httpGet('/', (request, response) => { + assert(spy.callCount === 1); + + app.httpPost('/route1', (request, response) => { + assert(spy.callCount === 2); + done(); + }); + }); + }); + + it('middleware as object on path /route1', (done) => { + const middleware = frontexpress.Middleware('on path /route1'); + const spy = sinon.spy(middleware, 'updated'); + + const app = frontexpress(); + app.set('http-requester', requester); + app.use('/route1', middleware); + + app.httpGet('/route1', (request, response) => { + assert(spy.callCount === 1); + + app.httpPost('/route1', (request, response) => { + assert(spy.callCount === 2); + done(); + }); + }); + }); + + it('middleware as object on path / request fails', (done) => { + requester = new Requester(); + sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { + reject( + {uri, method, headers, data}, + {status: 401, statusText: 'not logged'} + ); + }); + + const middleware = frontexpress.Middleware('on path /'); + const spy = sinon.spy(middleware, 'failed'); + + const app = frontexpress(); + app.set('http-requester', requester); + app.use(middleware); + + app.httpGet('/', null, (request, response) => { + assert(spy.callCount === 1); + assert(response.status === 401); + done(); + }); + }); + + it('router on path /', (done) => { + const spy = sinon.spy(); + const router = frontexpress.Router(); + router + .get((request, response, next) => {spy();}) + .post((request, response, next) => {spy();}); + + const app = frontexpress(); + app.set('http-requester', requester); + app.use(router); + + app.httpGet('/', (request, response) => { + assert(spy.callCount === 1); + + app.httpPost('/route1', (request, response) => { + assert(spy.callCount === 2); + done(); + }); + }); + }); + + it('router on path /route1', (done) => { + const spy = sinon.spy(); + const router = frontexpress.Router(); + router + .get((request, response, next) => {spy();}) + .post((request, response, next) => {spy();}); + + const app = frontexpress(); + app.set('http-requester', requester); + app.use('/route1', router); + + app.httpGet('/route1', (request, response) => { + assert(spy.callCount === 1); + + app.httpPost('/route1', (request, response) => { + assert(spy.callCount === 2); + done(); + }); + }); + }); + + it('router with base uri', (done)=> { + const middleware = frontexpress.Middleware('get middleware'); + const spy = sinon.spy(middleware, 'updated'); + + const app = frontexpress(); + app.set('http-requester', requester); + + const router = frontexpress.Router(); + router.get('/subroute1', middleware); + + app.use('/route1', router); + + app.httpGet('/route1/subroute1', (request, response) => { + assert(spy.calledOnce); + done(); + }, + (request, response) => { + done(response); + }); + }); + + it('use two routers', (done)=> { + + const router1 = frontexpress.Router(); + const m1 = frontexpress.Middleware(); + const spy1 = sinon.spy(m1, 'updated'); + router1.get('/subroute1', m1); + + const router2 = frontexpress.Router(); + const m2 = frontexpress.Middleware(); + const spy2 = sinon.spy(m2, 'updated'); + router2.get('/subroute2', m2); + + assert(router1 !== router2); + + + const app = frontexpress(); + app.set('http-requester', requester); + app.use(/^\/route1/, router1); + app.use(/^\/route2/, router2); + + app.httpGet('/route1/subroute1', + (request, response) => { + assert(spy1.calledOnce); + app.httpGet('/route2/subroute2', (request, response) => { + assert(spy2.calledOnce); + done(); + }, + (request, response) => { + done(response); + }); + }, + (request, response) => { + done(response); + }); + }); + }); + + describe('get method', () => { + beforeEach(()=>{ + requester = new Requester(); + sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { + resolve( + {uri, method, headers, data}, + {status: 200, statusText: 'OK', responseText:''} + ); + }); + }); + + it('no arguments', () => { + const app = frontexpress(); + app.set('http-requester', requester); + assert.throws(app.get, TypeError); + }); + + it('bad arguments', () => { + const app = frontexpress(); + app.set('http-requester', requester); + 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) => { + const spy = sinon.spy(); + + const app = frontexpress(); + app.set('http-requester', requester); + app.get((request, respons, next) => {spy();}); + + app.httpGet('/', (request, response) => { + assert(spy.callCount === 1); + + app.httpPost('/route1', (request, response) => { + assert(spy.callCount === 1); + done(); + }); + }); + }); + + it('middleware as function on path /route1', (done) => { + const spy = sinon.spy(); + + const app = frontexpress(); + app.set('http-requester', requester); + app.get('/route1', (request, response, next) => {spy();}); + + app.httpGet('/route1', (request, response) => { + assert(spy.callCount === 1); + + app.httpPost('/route1', (request, response) => { + assert(spy.callCount === 1); + done(); + }); + }); + }); + + it('middleware as object on path /', (done) => { + const middleware = frontexpress.Middleware('on path /'); + const spy = sinon.spy(middleware, 'updated'); + + const app = frontexpress(); + app.set('http-requester', requester); + app.get(middleware); + + app.httpGet('/', (request, response) => { + assert(spy.callCount === 1); + + app.httpPost('/route1', (request, response) => { + assert(spy.callCount === 1); + done(); + }); + }); + }); + + it('middleware as object on path /route1', (done) => { + const middleware = frontexpress.Middleware('on path /route1'); + const spy = sinon.spy(middleware, 'updated'); + + const app = frontexpress(); + app.set('http-requester', requester); + app.get(middleware); + + app.httpGet('/route1', (request, response) => { + assert(spy.callCount === 1); + + app.httpPost('/route1', (request, response) => { + assert(spy.callCount === 1); + done(); + }); + }); + }); + }); + + describe('route method', () => { + beforeEach(()=>{ + requester = new Requester(); + sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { + resolve( + {uri, method, headers, data}, + {status: 200, statusText: 'OK', responseText:''} + ); + }); + }); + + it('on root path not specified', (done) => { + const spy = sinon.spy(); + + const app = frontexpress(); + app.set('http-requester', requester); + app.route().get((request, response, next) => {spy();}); + + app.httpGet('/', (request, response) => { + assert(spy.calledOnce); + app.httpPost('/', (request, response) => { + assert(spy.calledOnce); + done(); + }); + }); + + }); + + it('on root path /', (done) => { + const spy = sinon.spy(); + + const app = frontexpress(); + app.set('http-requester', requester); + app.route('/').get((request, response, next) => {spy();}); + + app.httpGet('/', (request, response) => { + assert(spy.calledOnce); + app.httpPost('/', (request, response) => { + assert(spy.calledOnce); + done(); + }); + }); + + }); + + it('on root path /route1', (done) => { + const spy = sinon.spy(); + + const app = frontexpress(); + app.set('http-requester', requester); + app.route('/route1').get((request, response, next) => {spy();}); + + app.httpGet('/route1', (request, response) => { + assert(spy.calledOnce); + app.httpPost('/route1', (request, response) => { + assert(spy.calledOnce); + done(); + }); + }); + + }); + + it('on root path undefined with sub path /subroute1', (done) => { + const spy = sinon.spy(); + + const app = frontexpress(); + app.set('http-requester', requester); + app.route().get('/subroute1', (request, response, next) => {spy();}); + + app.httpGet('/subroute1', (request, response) => { + assert(spy.calledOnce); + app.httpPost('/subroute1', (request, response) => { + assert(spy.calledOnce); + done(); + }); + }); + }); + + it('on root path / with sub path /subroute1', (done) => { + const spy = sinon.spy(); + + const app = frontexpress(); + app.set('http-requester', requester); + app.route('/').get('/subroute1', (request, response, next) => {spy();}); + + app.httpGet('/subroute1', (request, response) => { + assert(spy.calledOnce); + app.httpPost('/subroute1', (request, response) => { + assert(spy.calledOnce); + done(); + }); + }); + }); + + it('on root path /route1 with sub path /subroute1', (done) => { + const spy = sinon.spy(); + + const app = frontexpress(); + app.set('http-requester', requester); + app.route('/route1').get('/subroute1', (request, response, next) => {spy();}); + + app.httpGet('/route1/subroute1', (request, response) => { + assert(spy.calledOnce); + app.httpPost('/route1/subroute1', (request, response) => { + assert(spy.calledOnce); + done(); + }); + }); + }); + }); + + describe('middleware object lifecycle', () => { + beforeEach(()=>{ + requester = new Requester(); + sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { + resolve( + {uri, method, headers, data}, + {status: 200, statusText: 'OK', responseText:''} + ); + }); + }); + + it('http GET request', (done) => { + const app = frontexpress(); + app.set('http-requester', requester); + + const getMiddleware = frontexpress.Middleware('get middleware'); + const spy_get_entered = sinon.spy(getMiddleware, 'entered'); + const spy_get_updated = sinon.spy(getMiddleware, 'updated'); + const spy_get_exited = sinon.spy(getMiddleware, 'exited'); + const spy_get_next = sinon.spy(getMiddleware, 'next'); + + app.route('/route1').get(getMiddleware); + + app.httpGet({uri:'/route1', data: {p1: 'a', p2: 'b', p3: 'c'}}, (request, response) => { + assert(spy_get_entered.callCount === 1); + assert(spy_get_updated.callCount === 1); + assert(spy_get_exited.callCount === 0); + assert(spy_get_next.callCount === 2); + + assert(spy_get_entered.calledBefore(spy_get_updated)); + assert(spy_get_next.calledAfter(spy_get_entered)); + assert(spy_get_next.calledAfter(spy_get_updated)); + done(); + }, + (request, response) => { + done(response); + }); + }); + + it('http GET followed by http POST requests', (done) => { + const app = frontexpress(); + app.set('http-requester', requester); + + const allMiddleware = frontexpress.Middleware('all middleware'); + const spy_all_entered = sinon.spy(allMiddleware, 'entered'); + const spy_all_updated = sinon.spy(allMiddleware, 'updated'); + const spy_all_exited = sinon.spy(allMiddleware, 'exited'); + + const getMiddleware = frontexpress.Middleware('get middleware'); + const spy_get_entered = sinon.spy(getMiddleware, 'entered'); + const spy_get_updated = sinon.spy(getMiddleware, 'updated'); + const spy_get_exited = sinon.spy(getMiddleware, 'exited'); + + const postMiddleware = frontexpress.Middleware('post middleware'); + const spy_post_entered = sinon.spy(postMiddleware, 'entered'); + const spy_post_updated = sinon.spy(postMiddleware, 'updated'); + const spy_post_exited = sinon.spy(postMiddleware, 'exited'); + + app.route('/route1') + .all(allMiddleware) + .get(getMiddleware) + .post(postMiddleware); + + app.httpGet('/route1', (request, response) => { + assert(spy_all_entered.callCount === 1); + assert(spy_all_updated.callCount === 1); + assert(spy_all_exited.callCount === 0); + assert(spy_all_entered.calledBefore(spy_get_entered)); + assert(spy_all_entered.calledBefore(spy_post_entered)); + + assert(spy_get_entered.callCount === 1); + assert(spy_get_updated.callCount === 1); + assert(spy_get_exited.callCount === 0); + assert(spy_get_entered.calledBefore(spy_post_entered)); + + assert(spy_post_entered.callCount === 0); + assert(spy_post_updated.callCount === 0); + assert(spy_post_exited.callCount === 0); + + spy_all_entered.reset(); + spy_all_updated.reset(); + spy_all_exited.reset(); + + spy_get_entered.reset(); + spy_get_updated.reset(); + spy_get_exited.reset(); + + spy_post_entered.reset(); + spy_post_updated.reset(); + spy_post_exited.reset(); + + app.httpPost('/route1', (request, response) => { + assert(spy_all_entered.callCount === 1); + assert(spy_all_updated.callCount === 1); + assert(spy_all_exited.callCount === 1); + assert(spy_all_exited.calledBefore(spy_all_entered)); + assert(spy_all_exited.calledBefore(spy_all_updated)); + + assert(spy_get_entered.callCount === 0); + assert(spy_get_updated.callCount === 0); + assert(spy_get_exited.callCount === 1); + + assert(spy_post_entered.callCount === 1); + assert(spy_post_updated.callCount === 1); + assert(spy_post_exited.callCount === 0); + + done(); + }, (request, response) => { + done('should fail'); + }); + }, (request, response) => { + done('should fail'); + }); + }); + + it('request returning error', (done) => { + requester = new Requester(); + sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { + reject( + {uri, method, headers, data}, + {status: 404, statusText: 'page not found'} + ); + }); + const app = frontexpress(); + app.set('http-requester', requester); + + const getMiddleware = frontexpress.Middleware('get middleware'); + const spy_get_failed = sinon.spy(getMiddleware, 'failed'); + + + app.route('/route1').get(getMiddleware); + + app.httpGet('/route1', (request, response) => { + done('should fail'); + }, + (request, response) => { + assert(spy_get_failed.callCount === 1); + done(); + }); + }); + + it('next method', (done) => { + const app = frontexpress(); + app.set('http-requester', requester); + + const m1 = frontexpress.Middleware('m1'); + const m2 = frontexpress.Middleware('m2'); + m2.next = () => false; + const m3 = frontexpress.Middleware('m3'); + + const spy_m1 = sinon.spy(m1, 'updated'); + const spy_m2 = sinon.spy(m2, 'updated'); + const spy_m3 = sinon.spy(m3, 'updated'); + + app.route('/route1').get(m1).get(m2).get(m3); + app.httpGet('/route1', (request, response) => { + assert(spy_m1.calledOnce); + assert(spy_m2.calledOnce); + assert(spy_m3.callCount === 0); + done(); + }); + }); + }); + + describe('middleware as function', () => { + beforeEach(()=>{ + requester = new Requester(); + sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => { + resolve( + {uri, method, headers, data}, + {status: 200, statusText: 'OK', responseText:''} + ); + }); + }); + + it('next method', (done) => { + const app = frontexpress(); + app.set('http-requester', requester); + + const m1 = (req, res, next) => {next();}; + const m2 = (req, res, next) => {}; + const m3 = (req, res, next) => {next();}; + + const spy_m1 = sinon.spy(m1); + const spy_m2 = sinon.spy(m2); + const spy_m3 = sinon.spy(m3); + + app.route('/route1').get(spy_m1).get(spy_m2).get(spy_m3); + app.httpGet('/route1', (request, response) => { + assert(spy_m1.calledOnce); + assert(spy_m2.calledOnce); + assert(spy_m3.callCount === 0); + done(); + }); + }); + }); +}); diff --git a/test/frontexpress-test.js b/test/frontexpress-test.js index a67d734..16f3d3b 100644 --- a/test/frontexpress-test.js +++ b/test/frontexpress-test.js @@ -1,3 +1,4 @@ +/*eslint-env mocha*/ import {assert} from 'chai'; import sinon from 'sinon'; import frontexpress from '../lib/frontexpress'; diff --git a/test/readme-test.js b/test/readme-test.js new file mode 100644 index 0000000..c14ec93 --- /dev/null +++ b/test/readme-test.js @@ -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('Birds home page
'); + }); + + // react on about route + router.get('/about', (req, res) => { + spy_log('About birds
'); + }); + + 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('Birds home page
').calledOnce); + assert(spy_log.neverCalledWith('About birds
')); + + 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('About birds
').calledOnce); + assert(spy_log.neverCalledWith('Birds home page
')); + done(); + }); + }); + + }); +}); \ No newline at end of file diff --git a/test/requester-test.js b/test/requester-test.js index b16acb3..6094524 100755 --- a/test/requester-test.js +++ b/test/requester-test.js @@ -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, '', 'content!
'); - - 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, '', 'content!
'); - - 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 === 'content!
'); - 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, '', 'content!
'); - - 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, '', 'content!
'); - - 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, '', 'content!
'); - - 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, '', 'content!
'); + + 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, '', 'content!
'); + + 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 === 'content!
'); + 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, '', 'content!
'); + + 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, '', 'content!
'); + + 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, '', 'content!
'); + + 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(); + }); + }); + }); +}); diff --git a/test/router-test.js b/test/router-test.js index 83290bb..6011277 100755 --- a/test/router-test.js +++ b/test/router-test.js @@ -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); }); });