mirror of
https://gitlab.silvrtree.co.uk/martind2000/frontexpress.git
synced 2025-01-10 21:45:08 +00:00
made eslint stricter - added unit tests to cover sample in README
This commit is contained in:
parent
6bfd3d42e9
commit
0014025be4
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@ -0,0 +1,2 @@
|
||||
coverage
|
||||
dist
|
24
.eslintrc
24
.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
|
||||
}
|
||||
}
|
68
README.md
68
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 = '<p>Birds home page</p>';
|
||||
});
|
||||
});
|
||||
|
||||
// react on about route
|
||||
router.get('/about', (req, res) => {
|
||||
document.querySelector('.content').innerHTML = '<p>About birds</p>';
|
||||
});
|
||||
// react on about route
|
||||
router.get('/about', (req, res) => {
|
||||
document.querySelector('.content').innerHTML = '<p>About birds</p>';
|
||||
});
|
||||
|
||||
export default router;
|
||||
```
|
||||
|
@ -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);
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -11,6 +11,10 @@ class Route {
|
||||
}
|
||||
|
||||
get uri() {
|
||||
if (!this.uriPart && !this.method) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (this.uriPart instanceof RegExp) {
|
||||
return this.uriPart;
|
||||
}
|
||||
@ -69,6 +73,9 @@ export default class Router {
|
||||
|
||||
routes(uri, method) {
|
||||
return this._routes.filter((route) => {
|
||||
if (!route.uri && !route.method) {
|
||||
return true;
|
||||
}
|
||||
if (route.method !== method) {
|
||||
return false;
|
||||
}
|
||||
@ -105,9 +112,19 @@ export default class Router {
|
||||
});
|
||||
}
|
||||
|
||||
use(middleware) {
|
||||
if (!(middleware instanceof Middleware) && (typeof middleware !== 'function') ) {
|
||||
throw new TypeError('use method takes at least a middleware');
|
||||
}
|
||||
|
||||
this._add(new Route(this, undefined, undefined, middleware));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
all(...args) {
|
||||
if (args.length === 0) {
|
||||
throw new TypeError(`use all method takes at least a middleware`);
|
||||
throw new TypeError('use all method takes at least a middleware');
|
||||
}
|
||||
let middleware;
|
||||
|
||||
@ -118,7 +135,7 @@ export default class Router {
|
||||
}
|
||||
|
||||
if (!(middleware instanceof Middleware) && (typeof middleware !== 'function') ) {
|
||||
throw new TypeError(`use all method takes at least a middleware`);
|
||||
throw new TypeError('use all method takes at least a middleware');
|
||||
}
|
||||
|
||||
for (const method of Object.keys(HTTP_METHODS)) {
|
||||
@ -147,11 +164,11 @@ for (const method of Object.keys(HTTP_METHODS)) {
|
||||
}
|
||||
|
||||
if (uri && this._baseUri && this._baseUri instanceof RegExp) {
|
||||
throw new TypeError(`router contains a regexp cannot mix with route uri/regexp`);
|
||||
throw new TypeError('router contains a regexp cannot mix with route uri/regexp');
|
||||
}
|
||||
|
||||
this._add(new Route(this, uri, method, middleware));
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,4 @@
|
||||
/*eslint-env mocha*/
|
||||
import {assert} from 'chai';
|
||||
import sinon from 'sinon';
|
||||
import frontexpress from '../lib/frontexpress';
|
||||
|
151
test/readme-test.js
Normal file
151
test/readme-test.js
Normal file
@ -0,0 +1,151 @@
|
||||
/*eslint-env mocha*/
|
||||
import chai, {assert} from 'chai';
|
||||
import sinon from 'sinon';
|
||||
import frontexpress from '../lib/frontexpress';
|
||||
import Requester from '../lib/requester';
|
||||
|
||||
describe('Test sample from README', () => {
|
||||
let window, requester;
|
||||
|
||||
beforeEach(() => {
|
||||
global.document = {};
|
||||
global.window = {
|
||||
location: {
|
||||
pathname: '/',
|
||||
search: ''
|
||||
},
|
||||
addEventListener(eventType, callback) {}
|
||||
};
|
||||
requester = new Requester();
|
||||
sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => {
|
||||
resolve(
|
||||
{uri, method, headers, data},
|
||||
{status: 200, statusText: 'OK', responseText:''}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('main sample', (done) => {
|
||||
const app = frontexpress();
|
||||
|
||||
// listen HTTP GET request on path (/)
|
||||
app.get('/', (req, res) => {
|
||||
done();
|
||||
});
|
||||
|
||||
// start listening frontend application requests
|
||||
app.listen();
|
||||
|
||||
//simulate readystatechange
|
||||
document.readyState = 'interactive';
|
||||
document.onreadystatechange();
|
||||
});
|
||||
|
||||
|
||||
it('route handlers', (done) => {
|
||||
const spy_log = sinon.spy();
|
||||
|
||||
const h1 = (req, res, next) => { spy_log('h1!'); next(); };
|
||||
const h2 = (req, res, next) => { spy_log('h2!');};
|
||||
const h3 = (req, res, next) => { spy_log('h3!'); next(); };
|
||||
|
||||
const app = frontexpress();
|
||||
app.set('http-requester', requester);
|
||||
|
||||
app.get('/example/a', h1);
|
||||
app.get('/example/a', h2);
|
||||
app.get('/example/a', h3);
|
||||
|
||||
// start listening frontend application requests
|
||||
app.listen();
|
||||
|
||||
// simulate readystatechange
|
||||
document.readyState = 'interactive';
|
||||
document.onreadystatechange();
|
||||
|
||||
// make an ajax request on /example/a
|
||||
app.httpGet('/example/a', (req, res) => {
|
||||
assert(spy_log.calledTwice);
|
||||
assert(spy_log.withArgs('h1!').calledOnce);
|
||||
assert(spy_log.withArgs('h2!').calledOnce);
|
||||
assert(spy_log.neverCalledWith('h3!'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('app.route()', (done) => {
|
||||
const spy_log = sinon.spy();
|
||||
|
||||
const app = frontexpress();
|
||||
app.set('http-requester', requester);
|
||||
|
||||
app.route('/book')
|
||||
.get((req, res) => { spy_log('Get a random book');})
|
||||
.post((req, res) => { spy_log('Add a book');})
|
||||
.put((req, res) => { spy_log('Update the book');});
|
||||
|
||||
// start listening frontend application requests
|
||||
app.listen();
|
||||
|
||||
// simulate readystatechange
|
||||
document.readyState = 'interactive';
|
||||
document.onreadystatechange();
|
||||
|
||||
// make an ajax request on /book
|
||||
app.httpGet('/book', (req, res) => {
|
||||
app.httpPost('/book', (req, res) => {
|
||||
app.httpPut('/book', (req, res) => {
|
||||
// Yes I know Promise :-) Next evolution
|
||||
assert(spy_log.callCount === 3);
|
||||
assert(spy_log.withArgs('Get a random book').calledOnce);
|
||||
assert(spy_log.withArgs('Add a book').calledOnce);
|
||||
assert(spy_log.withArgs('Update the book').calledOnce);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('frontexpress.Router', (done) => {
|
||||
const spy_log = sinon.spy();
|
||||
|
||||
const router = frontexpress.Router();
|
||||
|
||||
// middleware that is specific to this router
|
||||
router.use((req, res, next) => {
|
||||
spy_log(`Time: ${Date.now()}`);
|
||||
next();
|
||||
});
|
||||
|
||||
// react on home page route
|
||||
router.get('/', (req, res) => {
|
||||
spy_log('<p>Birds home page</p>');
|
||||
});
|
||||
|
||||
// react on about route
|
||||
router.get('/about', (req, res) => {
|
||||
spy_log('<p>About birds</p>');
|
||||
});
|
||||
|
||||
const app = frontexpress();
|
||||
app.set('http-requester', requester);
|
||||
app.use('/birds', router);
|
||||
|
||||
// make an ajax request on /birds
|
||||
app.httpGet('/birds/', (req, res) => {
|
||||
assert(spy_log.callCount === 2);
|
||||
assert(spy_log.withArgs('<p>Birds home page</p>').calledOnce);
|
||||
assert(spy_log.neverCalledWith('<p>About birds</p>'));
|
||||
|
||||
spy_log.reset();
|
||||
// make an ajax request on /birds/about
|
||||
app.httpGet('/birds/about', (req, res) => {
|
||||
assert(spy_log.callCount === 2);
|
||||
assert(spy_log.withArgs('<p>About birds</p>').calledOnce);
|
||||
assert(spy_log.neverCalledWith('<p>Birds home page</p>'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -1,400 +1,400 @@
|
||||
/*eslint-env mocha*/
|
||||
/*global global*/
|
||||
import {assert} from 'chai';
|
||||
import sinon from 'sinon';
|
||||
import FakeXMLHttpRequest from 'fake-xml-http-request';
|
||||
import Requester, {HTTP_METHODS} from '../lib/requester';
|
||||
|
||||
describe('HTTP_METHODS', () => {
|
||||
it('GET method simple uri', () => {
|
||||
const uriFn = HTTP_METHODS['GET'].uri;
|
||||
const dataFn = HTTP_METHODS['GET'].data;
|
||||
|
||||
assert(uriFn({uri: '/route', data:{a:'b', c:'d'}}) === '/route?a=b&c=d');
|
||||
assert(dataFn({uri: '/route', data:{a:'b', c:'d'}}) === undefined);
|
||||
});
|
||||
|
||||
it('GET method uri with query string', () => {
|
||||
const uriFn = HTTP_METHODS['GET'].uri;
|
||||
const dataFn = HTTP_METHODS['GET'].data;
|
||||
|
||||
assert(uriFn({uri: '/route?x=y&z=a', data:{a:'b', c:'d'}}) === '/route?x=y&z=a&a=b&c=d');
|
||||
assert(dataFn({uri: '/route?x=y&z=a', data:{a:'b', c:'d'}}) === undefined);
|
||||
});
|
||||
|
||||
it('GET method uri with query string and anchor', () => {
|
||||
const uriFn = HTTP_METHODS['GET'].uri;
|
||||
const dataFn = HTTP_METHODS['GET'].data;
|
||||
|
||||
assert(uriFn({uri: '/route?x=y&z=a#anchor1', data:{a:'b', c:'d'}}) === '/route?x=y&z=a&a=b&c=d#anchor1');
|
||||
assert(dataFn({uri: '/route?x=y&z=a#anchor1', data:{a:'b', c:'d'}}) === undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Requester', () => {
|
||||
function xHttpWillRespond(xhttp, readyState, status, statusText, responseText) {
|
||||
const stub_send = sinon.stub(xhttp, 'send', function() {
|
||||
this.readyState = readyState;
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.onreadystatechange()
|
||||
});
|
||||
|
||||
const stub_open = sinon.stub(xhttp, 'open', function() {});
|
||||
|
||||
const stub_setRequestHeader = sinon.stub(xhttp, 'setRequestHeader', function() {});
|
||||
|
||||
return {stub_open, stub_send, stub_setRequestHeader};
|
||||
}
|
||||
|
||||
function xHttpWillThrow(xhttp, sendErrorName, openErrorName) {
|
||||
const stub_send = sinon.stub(xhttp, 'send', function() {
|
||||
if (sendErrorName) {
|
||||
throw {name: sendErrorName};
|
||||
}
|
||||
});
|
||||
|
||||
const stub_open = sinon.stub(xhttp, 'open', function() {
|
||||
if (openErrorName) {
|
||||
throw {name: openErrorName};
|
||||
}
|
||||
});
|
||||
|
||||
return {stub_open, stub_send};
|
||||
}
|
||||
|
||||
let xhttp;
|
||||
|
||||
beforeEach(() => {
|
||||
// Stub XMLHttpRequest
|
||||
xhttp = new FakeXMLHttpRequest();
|
||||
global.XMLHttpRequest = () => {
|
||||
return xhttp;
|
||||
};
|
||||
});
|
||||
|
||||
describe('GET Requests', () => {
|
||||
|
||||
it('with data', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1', data:{p1: 'a', p2: 'b', p3: 'c'}},
|
||||
(request, response) => {
|
||||
assert(request.uri === '/route1?p1=a&p2=b&p3=c');
|
||||
done();
|
||||
},
|
||||
(err) => {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('without data', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, (request, response) => {
|
||||
assert(stub_open.calledOnce);
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledBefore(stub_send));
|
||||
|
||||
assert(request.uri === '/route1');
|
||||
assert(request.method === 'GET');
|
||||
assert(request.data === undefined);
|
||||
|
||||
assert(response.status === 200);
|
||||
assert(response.statusText === 'OK');
|
||||
assert(response.responseText === '<p>content!</p>');
|
||||
assert(response.errorThrown === undefined);
|
||||
assert(response.errors === undefined);
|
||||
|
||||
done();
|
||||
},
|
||||
(error) => {
|
||||
done(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST Requests', () => {
|
||||
|
||||
it('with data', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send, stub_setRequestHeader} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
|
||||
|
||||
requester.fetch({method: 'POST', uri:'/route1', data:{p1: 'a', p2: 'b', p3: 'c'}},
|
||||
(request, response) => {
|
||||
assert(stub_open.calledOnce);
|
||||
assert(stub_setRequestHeader.calledOnce);
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledBefore(stub_send));
|
||||
assert(stub_setRequestHeader.calledAfter(stub_open));
|
||||
assert(stub_setRequestHeader.calledBefore(stub_send));
|
||||
|
||||
assert(request.uri === '/route1');
|
||||
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
|
||||
assert(request.data === 'p1=a&p2=b&p3=c');
|
||||
done();
|
||||
},
|
||||
(err) => {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('without data', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send, stub_setRequestHeader} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
|
||||
|
||||
requester.fetch({method: 'POST', uri:'/route1'},
|
||||
(request, response) => {
|
||||
assert(stub_open.calledOnce);
|
||||
assert(stub_setRequestHeader.calledOnce);
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledBefore(stub_send));
|
||||
assert(stub_setRequestHeader.calledAfter(stub_open));
|
||||
assert(stub_setRequestHeader.calledBefore(stub_send));
|
||||
|
||||
assert(request.uri === '/route1');
|
||||
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
|
||||
assert(request.data === undefined);
|
||||
done();
|
||||
},
|
||||
(err) => {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('with custom headers', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send, stub_setRequestHeader} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
|
||||
|
||||
requester.fetch({method: 'POST', uri:'/route1', headers: {'Accept-Charset': 'utf-8'}},
|
||||
(request, response) => {
|
||||
assert(stub_open.calledOnce);
|
||||
assert(stub_setRequestHeader.calledTwice);
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledBefore(stub_send));
|
||||
assert(stub_setRequestHeader.calledAfter(stub_open));
|
||||
assert(stub_setRequestHeader.calledBefore(stub_send));
|
||||
|
||||
assert(request.uri === '/route1');
|
||||
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
|
||||
assert(request.headers['Accept-Charset'] === 'utf-8');
|
||||
assert(request.data === undefined);
|
||||
done();
|
||||
},
|
||||
(err) => {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('HTTP errors', () => {
|
||||
it('request returns no network', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 0);
|
||||
|
||||
requester.fetch({method: 'GET', uri: '/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_open.calledOnce);
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledBefore(stub_send));
|
||||
|
||||
assert(response.status === 0);
|
||||
assert(response.statusText === undefined);
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown === undefined);
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns 401', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 401, 'not authenticated', '');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === 401);
|
||||
assert(response.statusText === 'not authenticated');
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown === undefined);
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns 404', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 404, 'page not found', '');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === 404);
|
||||
assert(response.statusText === 'page not found');
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown === undefined);
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns 500', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 500, 'server error', '');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === 500);
|
||||
assert(response.statusText === 'server error');
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown === undefined);
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns 501 (http status not managed)', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 501, 'Not Implemented', '');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === 501);
|
||||
assert(response.statusText === 'Not Implemented');
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown === undefined);
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns syntax error', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'SyntaxError');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === undefined);
|
||||
assert(response.statusText === undefined);
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown.name === 'SyntaxError');
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns timeout error', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'TimeoutError');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === undefined);
|
||||
assert(response.statusText === undefined);
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown.name === 'TimeoutError');
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns abort error', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'AbortError');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === undefined);
|
||||
assert(response.statusText === undefined);
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown.name === 'AbortError');
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns network error', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'NetworkError');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === undefined);
|
||||
assert(response.statusText === undefined);
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown.name === 'NetworkError');
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns unknown error', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'BlaBlaError');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === undefined);
|
||||
assert(response.statusText === undefined);
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown.name === 'BlaBlaError');
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
/*eslint-env mocha*/
|
||||
/*global global*/
|
||||
import {assert} from 'chai';
|
||||
import sinon from 'sinon';
|
||||
import FakeXMLHttpRequest from 'fake-xml-http-request';
|
||||
import Requester, {HTTP_METHODS} from '../lib/requester';
|
||||
|
||||
describe('HTTP_METHODS', () => {
|
||||
it('GET method simple uri', () => {
|
||||
const uriFn = HTTP_METHODS['GET'].uri;
|
||||
const dataFn = HTTP_METHODS['GET'].data;
|
||||
|
||||
assert(uriFn({uri: '/route', data:{a:'b', c:'d'}}) === '/route?a=b&c=d');
|
||||
assert(dataFn({uri: '/route', data:{a:'b', c:'d'}}) === undefined);
|
||||
});
|
||||
|
||||
it('GET method uri with query string', () => {
|
||||
const uriFn = HTTP_METHODS['GET'].uri;
|
||||
const dataFn = HTTP_METHODS['GET'].data;
|
||||
|
||||
assert(uriFn({uri: '/route?x=y&z=a', data:{a:'b', c:'d'}}) === '/route?x=y&z=a&a=b&c=d');
|
||||
assert(dataFn({uri: '/route?x=y&z=a', data:{a:'b', c:'d'}}) === undefined);
|
||||
});
|
||||
|
||||
it('GET method uri with query string and anchor', () => {
|
||||
const uriFn = HTTP_METHODS['GET'].uri;
|
||||
const dataFn = HTTP_METHODS['GET'].data;
|
||||
|
||||
assert(uriFn({uri: '/route?x=y&z=a#anchor1', data:{a:'b', c:'d'}}) === '/route?x=y&z=a&a=b&c=d#anchor1');
|
||||
assert(dataFn({uri: '/route?x=y&z=a#anchor1', data:{a:'b', c:'d'}}) === undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Requester', () => {
|
||||
function xHttpWillRespond(xhttp, readyState, status, statusText, responseText) {
|
||||
const stub_send = sinon.stub(xhttp, 'send', function() {
|
||||
this.readyState = readyState;
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.onreadystatechange();
|
||||
});
|
||||
|
||||
const stub_open = sinon.stub(xhttp, 'open', function() {});
|
||||
|
||||
const stub_setRequestHeader = sinon.stub(xhttp, 'setRequestHeader', function() {});
|
||||
|
||||
return {stub_open, stub_send, stub_setRequestHeader};
|
||||
}
|
||||
|
||||
function xHttpWillThrow(xhttp, sendErrorName, openErrorName) {
|
||||
const stub_send = sinon.stub(xhttp, 'send', function() {
|
||||
if (sendErrorName) {
|
||||
throw {name: sendErrorName};
|
||||
}
|
||||
});
|
||||
|
||||
const stub_open = sinon.stub(xhttp, 'open', function() {
|
||||
if (openErrorName) {
|
||||
throw {name: openErrorName};
|
||||
}
|
||||
});
|
||||
|
||||
return {stub_open, stub_send};
|
||||
}
|
||||
|
||||
let xhttp;
|
||||
|
||||
beforeEach(() => {
|
||||
// Stub XMLHttpRequest
|
||||
xhttp = new FakeXMLHttpRequest();
|
||||
global.XMLHttpRequest = () => {
|
||||
return xhttp;
|
||||
};
|
||||
});
|
||||
|
||||
describe('GET Requests', () => {
|
||||
|
||||
it('with data', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1', data:{p1: 'a', p2: 'b', p3: 'c'}},
|
||||
(request, response) => {
|
||||
assert(request.uri === '/route1?p1=a&p2=b&p3=c');
|
||||
done();
|
||||
},
|
||||
(err) => {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('without data', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, (request, response) => {
|
||||
assert(stub_open.calledOnce);
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledBefore(stub_send));
|
||||
|
||||
assert(request.uri === '/route1');
|
||||
assert(request.method === 'GET');
|
||||
assert(request.data === undefined);
|
||||
|
||||
assert(response.status === 200);
|
||||
assert(response.statusText === 'OK');
|
||||
assert(response.responseText === '<p>content!</p>');
|
||||
assert(response.errorThrown === undefined);
|
||||
assert(response.errors === undefined);
|
||||
|
||||
done();
|
||||
},
|
||||
(error) => {
|
||||
done(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST Requests', () => {
|
||||
|
||||
it('with data', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send, stub_setRequestHeader} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
|
||||
|
||||
requester.fetch({method: 'POST', uri:'/route1', data:{p1: 'a', p2: 'b', p3: 'c'}},
|
||||
(request, response) => {
|
||||
assert(stub_open.calledOnce);
|
||||
assert(stub_setRequestHeader.calledOnce);
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledBefore(stub_send));
|
||||
assert(stub_setRequestHeader.calledAfter(stub_open));
|
||||
assert(stub_setRequestHeader.calledBefore(stub_send));
|
||||
|
||||
assert(request.uri === '/route1');
|
||||
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
|
||||
assert(request.data === 'p1=a&p2=b&p3=c');
|
||||
done();
|
||||
},
|
||||
(err) => {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('without data', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send, stub_setRequestHeader} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
|
||||
|
||||
requester.fetch({method: 'POST', uri:'/route1'},
|
||||
(request, response) => {
|
||||
assert(stub_open.calledOnce);
|
||||
assert(stub_setRequestHeader.calledOnce);
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledBefore(stub_send));
|
||||
assert(stub_setRequestHeader.calledAfter(stub_open));
|
||||
assert(stub_setRequestHeader.calledBefore(stub_send));
|
||||
|
||||
assert(request.uri === '/route1');
|
||||
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
|
||||
assert(request.data === undefined);
|
||||
done();
|
||||
},
|
||||
(err) => {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('with custom headers', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send, stub_setRequestHeader} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
|
||||
|
||||
requester.fetch({method: 'POST', uri:'/route1', headers: {'Accept-Charset': 'utf-8'}},
|
||||
(request, response) => {
|
||||
assert(stub_open.calledOnce);
|
||||
assert(stub_setRequestHeader.calledTwice);
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledBefore(stub_send));
|
||||
assert(stub_setRequestHeader.calledAfter(stub_open));
|
||||
assert(stub_setRequestHeader.calledBefore(stub_send));
|
||||
|
||||
assert(request.uri === '/route1');
|
||||
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
|
||||
assert(request.headers['Accept-Charset'] === 'utf-8');
|
||||
assert(request.data === undefined);
|
||||
done();
|
||||
},
|
||||
(err) => {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('HTTP errors', () => {
|
||||
it('request returns no network', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 0);
|
||||
|
||||
requester.fetch({method: 'GET', uri: '/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_open.calledOnce);
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledBefore(stub_send));
|
||||
|
||||
assert(response.status === 0);
|
||||
assert(response.statusText === undefined);
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown === undefined);
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns 401', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 401, 'not authenticated', '');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === 401);
|
||||
assert(response.statusText === 'not authenticated');
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown === undefined);
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns 404', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 404, 'page not found', '');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === 404);
|
||||
assert(response.statusText === 'page not found');
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown === undefined);
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns 500', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 500, 'server error', '');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === 500);
|
||||
assert(response.statusText === 'server error');
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown === undefined);
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns 501 (http status not managed)', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 501, 'Not Implemented', '');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === 501);
|
||||
assert(response.statusText === 'Not Implemented');
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown === undefined);
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns syntax error', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'SyntaxError');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === undefined);
|
||||
assert(response.statusText === undefined);
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown.name === 'SyntaxError');
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns timeout error', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'TimeoutError');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === undefined);
|
||||
assert(response.statusText === undefined);
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown.name === 'TimeoutError');
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns abort error', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'AbortError');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === undefined);
|
||||
assert(response.statusText === undefined);
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown.name === 'AbortError');
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns network error', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'NetworkError');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === undefined);
|
||||
assert(response.statusText === undefined);
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown.name === 'NetworkError');
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('request returns unknown error', (done) => {
|
||||
const requester = new Requester();
|
||||
|
||||
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'BlaBlaError');
|
||||
|
||||
requester.fetch({method: 'GET', uri:'/route1'}, null,
|
||||
(request, response) => {
|
||||
assert(stub_send.calledOnce);
|
||||
assert(stub_open.calledOnce);
|
||||
|
||||
assert(response.status === undefined);
|
||||
assert(response.statusText === undefined);
|
||||
assert(response.responseText === undefined);
|
||||
assert(response.errorThrown.name === 'BlaBlaError');
|
||||
assert(response.errors.length !== 0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user