fixed issues #5 #6

This commit is contained in:
Camel Aissani 2017-06-22 21:17:18 +02:00
parent 34b7580d18
commit a1e56dc6c3
14 changed files with 938 additions and 571 deletions

269
README.md
View File

@ -1,38 +1,14 @@
![frontexpress](http://fontmeme.com/embed.php?text=frontexpress&name=Atype%201%20Light.ttf&size=90&style_color=6F6F75)
Code the front-end like on the back-end with [ExpressJS](http://expressjs.com/)
[frontexpress demo](https://github.com/camelaissani/frontexpress-demo) repository.
[![Build Status](https://travis-ci.org/camelaissani/frontexpress.svg?branch=master)](https://travis-ci.org/camelaissani/frontexpress)
[![Code Climate](https://codeclimate.com/github/camelaissani/frontexpress/badges/gpa.svg)](https://codeclimate.com/github/camelaissani/frontexpress)
[![Coverage Status](https://coveralls.io/repos/github/camelaissani/frontexpress/badge.svg?branch=master)](https://coveralls.io/github/camelaissani/frontexpress?branch=master)
![dependencies](https://img.shields.io/gemnasium/mathiasbynens/he.svg)
![Size Shield](https://img.shields.io/badge/size-2.86kb-brightgreen.svg)
Frontexpress manages routes in browser like [ExpressJS](http://expressjs.com/) does on Node.
Same language same API on all the stack.
Code the front-end logic with the same style than on the back-end with express
```js
import frontexpress from 'frontexpress';
// Front-end application
const app = frontexpress();
// front-end logic on navigation path "/page1"
app.get('/page1', (req, res) => {
document.querySelector('.content').innerHTML = `<h1>Page 1 content</h1>`;
});
// front-end logic on navigation path "/page2"
app.get('/page2', (req, res) => {
document.querySelector('.content').innerHTML = `<h1>Page 2 content</h1>`;
});
// start front-end application
app.listen(() => {
// on DOM ready
});
```
![Size Shield](https://img.shields.io/badge/size-3.26kb-brightgreen.svg)
## Installation
@ -52,43 +28,31 @@ $ bower install frontexpress
On [jsDelivr](https://cdn.jsdelivr.net/npm/frontexpress@1.1.0/frontexpress.min.js)
## Quick Start
## Usage
The quickest way to get started with frontexpress is to clone the [frontexpress-demo](https://github.com/camelaissani/frontexpress-demo) repository.
```js
import frontexpress from 'frontexpress';
## Tests
// Front-end application
const app = frontexpress();
Clone the git repository:
// front-end logic on navigation path "/page1"
app.get('/page1', (req, res) => {
document.querySelector('.content').innerHTML = res.responseText;
});
```bash
$ git clone git@github.com:camelaissani/frontexpress.git
$ cd frontexpress
// front-end logic on navigation path "/page2"
app.get('/page2', (req, res) => {
document.querySelector('.content').innerHTML = res.responseText;
});
// start front-end application
app.listen();
```
Install the dependencies and run the test suite:
### Routes
```bash
$ npm install
$ npm test
```
## Navigation path and frontexpress routing
### Disclaimer
>
> In this first version of frontexpress, the API is not completely the mirror of the expressjs one.
>
> There are some missing methods. Currently, the use, get, post... methods having a middleware array as parameter are not available.
> The string pattern to define route paths is not yet implemented.
>
> Obviously, the objective is to have the same API as expressjs when the methods make sense browser side.
>
### Basic routing
Listen navigation (GET request) on path /hello:
Listen GET requests on path /hello:
```js
app.get('/hello', (req, res) => {
@ -96,7 +60,7 @@ app.get('/hello', (req, res) => {
});
```
Listen a POST request on path /item:
Listen POST requests on path /item:
```js
app.post('/item', (req, res) => {
@ -104,9 +68,7 @@ app.post('/item', (req, res) => {
});
```
### Routing based on RegExp
Listen navigation on paths which start with /api/:
Listen GET requests on path starting with /api/:
```js
app.get(/^api\//, (req, res) => {
@ -114,6 +76,66 @@ app.get(/^api\//, (req, res) => {
});
```
Get parameters from path
```js
app.get('/product/:id', (req, res) => {
// if we have /product/42 then
// req.params.id = 42
});
```
```js
app.get('/user/:firstname?/:lastname', (req, res) => {
// if we have /user/camel/aissani then
// req.params.firstname = 'camel'
// req.params.lastname = 'aissani'
// if we have /user/aissani then
// req.params.firstname = undefined
// req.params.lastname = 'aissani'
});
```
```js
app.get('/user/:id', (req, res) => {
// if we have /user/1,2,3 then
// req.params.id = [1,2,3]
});
```
### Middleware object
The middleware object gives access to more hooks
```js
class MyMiddleware = new Middleware {
entered(req) {
// before request sent
}
updated(req, res) {
// after request sent
// res has the request response
window.alert('Hello World');
}
exited(req) {
// before a new request sent
}
failed(req, res) {
// on request failed
}
next() {
// for chaining
return true;
}
}
app.get('/hello', new MyMiddleware());
```
### Chain handlers
You can provide multiple handlers functions on a navigation path. Invoking ```next()``` function allows to chain the handlers.
@ -138,7 +160,7 @@ h2!
h3 is ignored because ```next()``` function was not invoked.
### app.route()
#### app.route()
You can create chainable route handlers for a route path by using ```app.route()```.
@ -149,7 +171,7 @@ app.route('/book')
.put((req, res) => { console.log('Update the book') });
```
### frontexpress.Router
#### frontexpress.Router
Use the ```frontexpress.Router``` class to create modular, mountable route handlers.
@ -187,121 +209,24 @@ import birds from './birds';
app.use('/birds', birds);
```
## API
## [API](https://github.com/camelaissani/frontexpress/blob/master/docs/api.md)
| | Method | Short description |
| :------------- | :--------------| :----------------- |
|Frontexpress |||
||[frontexpress()](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpress-1)|Creates an instance of application|
||[frontexpress.Router()](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpressrouter)|Creates a Router object|
||[frontexpress.Middleware](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpressmiddleware)|Returns the Middleware class |
||||
|Application |||
||[set(setting, value)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationsetsetting-val)|Assigns a setting|
||[listen(callback)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationlistencallback)|Starts the application|
||[route(uri)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationrouteuri)|Gets a Router initialized with a root path|
||[use(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationuseuri-middleware)|Sets a middleware|
||||
||[get(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a GET request|
||[post(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a POST request|
||[put(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a PUT request|
||[delete(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a DELETE request|
||||
||[httpGet(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a GET ajax request|
||[httpPost(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a POST ajax request|
||[httpPut(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a PUT ajax request|
||[httpDelete(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a DELETE ajax request|
||||
|Router |||
||[use(middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routerusemiddleware)|Sets a middleware|
||[all(middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routerallmiddleware)|Sets a middleware on all HTTP method requests|
||||
||[get(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a GET request|
||[post(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a POST request|
||[put(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a PUT request|
||[delete(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a DELETE request|
||||
|Middleware |||
||[entered(request)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareenteredrequest)|Invoked by the app before an ajax request is sent|
||[exited(request)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareexitedrequest)|Invoked by the app before a new ajax request is sent|
||[updated(request, response)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareupdatedrequest-response)|Invoked by the app after an ajax request has responded|
||[failed(request, response)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewarefailedrequest-response)|Invoked by the app after an ajax request has failed|
||[next()](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewarenext)|Allows to break the middleware chain execution|
## Tests
Clone the git repository:
### middleware function
After registering a middleware function, the application invokes it with these parameters:
```js
(request, response, next) => {
next();
}
```bash
$ git clone git@github.com:camelaissani/frontexpress.git
$ cd frontexpress
```
**request**: `Object`, the ajax request information sent by the app
Install the dependencies and run the test suite:
**response**: `Object`, the response of request
**next**: `Function`, the `next()` function to call to not break the middleware execution chain
### request object
```js
{
method,
uri,
headers,
data,
history: {
state,
title,
uri
}
}
```bash
$ npm install
$ npm test
```
**method**: `String`, HTTP methods 'GET', 'POST'...
**uri**: `String`, path
**headers**: `Object`, custom HTTP headers
**data**: `Object`, data attached to the request
**history**: `Object`, object with properties state, title and uri
**If the history object is set, it will activate the browser history management.** See [browser pushState() method](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState()_method) for more information about state, title, and uri (url).
> uri and history.uri can be different.
### response object
```js
{
status,
statusText,
responseText,
errorThrown,
errors
}
```
**status**: `Number`, HTTP status 200, 404, 401, 500...
**statusText**: `String`
**responseText**: `String` response content
**errorThrown**: `Object` exception thrown (if request fails)
**errors**: `String` error description (if request fails)
## License
[MIT](LICENSE)

109
docs/api.md Normal file
View File

@ -0,0 +1,109 @@
# API
| | Method | Short description |
| :------------- | :--------------| :----------------- |
|Frontexpress |||
||[frontexpress()](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpress-1)|Creates an instance of application|
||[frontexpress.Router()](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpressrouter)|Creates a Router object|
||[frontexpress.Middleware](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpressmiddleware)|Returns the Middleware class |
||||
|Application |||
||[set(setting, value)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationsetsetting-val)|Assigns a setting|
||[listen(callback)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationlistencallback)|Starts the application|
||[route(uri)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationrouteuri)|Gets a Router initialized with a root path|
||[use(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationuseuri-middleware)|Sets a middleware|
||||
||[get(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a GET request|
||[post(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a POST request|
||[put(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a PUT request|
||[delete(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a DELETE request|
||||
||[httpGet(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a GET ajax request|
||[httpPost(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a POST ajax request|
||[httpPut(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a PUT ajax request|
||[httpDelete(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a DELETE ajax request|
||||
|Router |||
||[use(middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routerusemiddleware)|Sets a middleware|
||[all(middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routerallmiddleware)|Sets a middleware on all HTTP method requests|
||||
||[get(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a GET request|
||[post(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a POST request|
||[put(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a PUT request|
||[delete(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a DELETE request|
||||
|Middleware |||
||[entered(request)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareenteredrequest)|Invoked by the app before an ajax request is sent|
||[exited(request)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareexitedrequest)|Invoked by the app before a new ajax request is sent|
||[updated(request, response)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareupdatedrequest-response)|Invoked by the app after an ajax request has responded|
||[failed(request, response)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewarefailedrequest-response)|Invoked by the app after an ajax request has failed|
||[next()](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewarenext)|Allows to break the middleware chain execution|
# middleware function
After registering a middleware function, the application invokes it with these parameters:
```js
(request, response, next) => {
next();
}
```
**request**: `Object`, the ajax request information sent by the app
**response**: `Object`, the response of request
**next**: `Function`, the `next()` function to call to not break the middleware execution chain
# request object
```js
{
method,
uri,
headers,
data,
history: {
state,
title,
uri
}
}
```
**method**: `String`, HTTP methods 'GET', 'POST'...
**uri**: `String`, path
**headers**: `Object`, custom HTTP headers
**data**: `Object`, data attached to the request
**history**: `Object`, object with properties state, title and uri
>**If the history object is set, it will activate the browser history management.** See [browser pushState() method](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState()_method) for more information about state, title, and uri (url).
> uri and history.uri can be different.
**params**: `Object`, object containing the path parameters
# response object
```js
{
status,
statusText,
responseText,
errorThrown,
errors
}
```
**status**: `Number`, HTTP status 200, 404, 401, 500...
**statusText**: `String`
**responseText**: `String` response content
**errorThrown**: `Object` exception thrown (if request fails)
**errors**: `String` error description (if request fails)

View File

@ -136,200 +136,6 @@ var toConsumableArray = function (arr) {
}
};
/**
* Module dependencies.
* @private
*/
var Requester = function () {
function Requester() {
classCallCheck(this, Requester);
}
createClass(Requester, [{
key: 'fetch',
/**
* Make an ajax request.
*
* @param {Object} request
* @param {Function} success callback
* @param {Function} failure callback
* @private
*/
value: function fetch(request, resolve, reject) {
var method = request.method,
uri = request.uri,
headers = request.headers,
data = request.data;
var success = function success(responseText) {
resolve(request, {
status: 200,
statusText: 'OK',
responseText: responseText
});
};
var fail = function fail(_ref) {
var status = _ref.status,
statusText = _ref.statusText,
errorThrown = _ref.errorThrown;
reject(request, {
status: status,
statusText: statusText,
errorThrown: errorThrown,
errors: 'HTTP ' + status + ' ' + (statusText ? statusText : '')
});
};
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4) {
//XMLHttpRequest.DONE
if (xmlhttp.status === 200) {
success(xmlhttp.responseText);
} else {
fail({ status: xmlhttp.status, statusText: xmlhttp.statusText });
}
}
};
try {
xmlhttp.open(method, uri, true);
if (headers) {
Object.keys(headers).forEach(function (header) {
xmlhttp.setRequestHeader(header, headers[header]);
});
}
if (data) {
xmlhttp.send(data);
} else {
xmlhttp.send();
}
} catch (errorThrown) {
fail({ errorThrown: errorThrown });
}
}
}]);
return Requester;
}();
/**
* Module dependencies.
* @private
*/
/**
* Settings object.
* @private
*/
var Settings = function () {
/**
* Initialize the settings.
*
* - setup default configuration
*
* @private
*/
function Settings() {
classCallCheck(this, Settings);
// default settings
this.settings = {
'http requester': new Requester(),
'http GET transformer': {
uri: function uri(_ref) {
var _uri = _ref.uri,
headers = _ref.headers,
data = _ref.data;
if (!data) {
return _uri;
}
var uriWithoutAnchor = _uri,
anchor = '';
var match = /^(.*)(#.*)$/.exec(_uri);
if (match) {
var _$exec = /^(.*)(#.*)$/.exec(_uri);
var _$exec2 = slicedToArray(_$exec, 3);
uriWithoutAnchor = _$exec2[1];
anchor = _$exec2[2];
}
uriWithoutAnchor = Object.keys(data).reduce(function (gUri, d, index) {
gUri += '' + (index === 0 && gUri.indexOf('?') === -1 ? '?' : '&') + d + '=' + data[d];
return gUri;
}, uriWithoutAnchor);
return uriWithoutAnchor + anchor;
}
}
// 'http POST transformer': {
// headers({uri, headers, data}) {
// if (!data) {
// return headers;
// }
// const updatedHeaders = headers || {};
// if (!updatedHeaders['Content-Type']) {
// updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
// }
// return updatedHeaders;
// }
// }
};
this.rules = {
'http requester': function httpRequester(requester) {
if (typeof requester.fetch !== 'function') {
throw new TypeError('setting http requester has no fetch method');
}
}
};
}
/**
* Assign `setting` to `val`
*
* @param {String} setting
* @param {*} [val]
* @private
*/
createClass(Settings, [{
key: 'set',
value: function set$$1(name, value) {
var checkRules = this.rules[name];
if (checkRules) {
checkRules(value);
}
this.settings[name] = value;
}
/**
* Return `setting`'s value.
*
* @param {String} setting
* @private
*/
}, {
key: 'get',
value: function get$$1(name) {
return this.settings[name];
}
}]);
return Settings;
}();
/**
* Middleware object.
* @public
@ -543,26 +349,11 @@ var Router = function () {
}, {
key: 'routes',
value: function routes(uri, method) {
value: function routes(application, request) {
request.params = request.params || {};
var isRouteMatch = application.get('route matcher');
return this._routes.filter(function (route) {
if (route.method && route.method !== method) {
return false;
}
if (!route.uri || !uri) {
return true;
}
//remove query string from uri to test
//remove anchor from uri to test
var match = /^(.*)\?.*#.*|(.*)(?=\?|#)|(.*[^\?#])$/.exec(uri);
var baseUriToCheck = match[1] || match[2] || match[3];
if (route.uri instanceof RegExp) {
return baseUriToCheck.match(route.uri);
}
return route.uri === baseUriToCheck;
return isRouteMatch(request, route);
});
}
@ -724,6 +515,284 @@ HTTP_METHODS.forEach(function (method) {
};
});
function routeMatcher(request, route) {
// check if http method are equals
if (route.method && route.method !== request.method) {
return false;
}
// route and uri not defined always match
if (!route.uri || !request.uri) {
return true;
}
//remove query string and anchor from uri to test
var match = /^(.*)\?.*#.*|(.*)(?=\?|#)|(.*[^\?#])$/.exec(request.uri);
var baseUriToCheck = match[1] || match[2] || match[3];
// if route is a regexp path
if (route.uri instanceof RegExp) {
return baseUriToCheck.match(route.uri) !== null;
}
// if route is parameterized path
if (route.uri.indexOf(':') !== -1) {
var decodeParmeterValue = function decodeParmeterValue(v) {
return !isNaN(parseFloat(v)) && isFinite(v) ? Number.isInteger(v) ? Number.parseInt(v, 10) : Number.parseFloat(v) : v;
};
// figure out key names
var keys = [];
var keysRE = /:([^\/\?]+)\??/g;
var keysMatch = keysRE.exec(route.uri);
while (keysMatch != null) {
keys.push(keysMatch[1]);
keysMatch = keysRE.exec(route.uri);
}
// change parameterized path to regexp
var regExpUri = route.uri
// :parameter?
.replace(/\/:[^\/]+\?/g, '(?:\/([^\/]+))?')
// :parameter
.replace(/:[^\/]+/g, '([^\/]+)')
// escape all /
.replace('/', '\\/');
// checks if uri match
var routeMatch = baseUriToCheck.match(new RegExp('^' + regExpUri + '$'));
if (!routeMatch) {
return false;
}
// update params in request with keys
request.params = Object.assign(request.params, keys.reduce(function (acc, key, index) {
var value = routeMatch[index + 1];
if (value) {
value = value.indexOf(',') !== -1 ? value.split(',').map(function (v) {
return decodeParmeterValue(v);
}) : value = decodeParmeterValue(value);
}
acc[key] = value;
return acc;
}, {}));
return true;
}
// if route is a simple path
return route.uri === baseUriToCheck;
}
/**
* Module dependencies.
* @private
*/
var Requester = function () {
function Requester() {
classCallCheck(this, Requester);
}
createClass(Requester, [{
key: 'fetch',
/**
* Make an ajax request.
*
* @param {Object} request
* @param {Function} success callback
* @param {Function} failure callback
* @private
*/
value: function fetch(request, resolve, reject) {
var method = request.method,
uri = request.uri,
headers = request.headers,
data = request.data;
var success = function success(responseText) {
resolve(request, {
status: 200,
statusText: 'OK',
responseText: responseText
});
};
var fail = function fail(_ref) {
var status = _ref.status,
statusText = _ref.statusText,
errorThrown = _ref.errorThrown;
reject(request, {
status: status,
statusText: statusText,
errorThrown: errorThrown,
errors: 'HTTP ' + status + ' ' + (statusText ? statusText : '')
});
};
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4) {
//XMLHttpRequest.DONE
if (xmlhttp.status === 200) {
success(xmlhttp.responseText);
} else {
fail({ status: xmlhttp.status, statusText: xmlhttp.statusText });
}
}
};
try {
xmlhttp.open(method, uri, true);
if (headers) {
Object.keys(headers).forEach(function (header) {
xmlhttp.setRequestHeader(header, headers[header]);
});
}
if (data) {
xmlhttp.send(data);
} else {
xmlhttp.send();
}
} catch (errorThrown) {
fail({ errorThrown: errorThrown });
}
}
}]);
return Requester;
}();
var httpGetTransformer = {
uri: function uri(_ref2) {
var _uri = _ref2.uri,
headers = _ref2.headers,
data = _ref2.data;
if (!data) {
return _uri;
}
var uriWithoutAnchor = _uri,
anchor = '';
var match = /^(.*)(#.*)$/.exec(_uri);
if (match) {
var _$exec = /^(.*)(#.*)$/.exec(_uri);
var _$exec2 = slicedToArray(_$exec, 3);
uriWithoutAnchor = _$exec2[1];
anchor = _$exec2[2];
}
uriWithoutAnchor = Object.keys(data).reduce(function (gUri, d, index) {
gUri += '' + (index === 0 && gUri.indexOf('?') === -1 ? '?' : '&') + d + '=' + data[d];
return gUri;
}, uriWithoutAnchor);
return uriWithoutAnchor + anchor;
}
};
// export const httpPostTransformer = {
// headers({uri, headers, data}) {
// if (!data) {
// return headers;
// }
// const updatedHeaders = headers || {};
// if (!updatedHeaders['Content-Type']) {
// updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
// }
// return updatedHeaders;
// }
// };
/**
* Module dependencies.
* @private
*/
function errorIfNotFunction(toTest, message) {
if (typeof toTest !== 'function') {
throw new TypeError(message);
}
}
/**
* Settings object.
* @private
*/
var Settings = function () {
/**
* Initialize the settings.
*
* - setup default configuration
*
* @private
*/
function Settings() {
classCallCheck(this, Settings);
// default settings
this.settings = {
'http requester': new Requester(),
'http GET transformer': httpGetTransformer,
// 'http POST transformer': httpPostTransformer,
'route matcher': routeMatcher
};
this.rules = {
'http requester': function httpRequester(requester) {
errorIfNotFunction(requester.fetch, 'setting http requester has no fetch function');
},
'http GET transformer': function httpGETTransformer(transformer) {
if (!transformer || !transformer.uri && !transformer.headers && !transformer.data) {
throw new TypeError('setting http transformer one of functions: uri, headers, data is missing');
}
},
'route matcher': function routeMatcher$$1(_routeMatcher) {
errorIfNotFunction(_routeMatcher, 'setting route matcher is not a function');
}
};
}
/**
* Assign `setting` to `val`
*
* @param {String} setting
* @param {*} [val]
* @private
*/
createClass(Settings, [{
key: 'set',
value: function set$$1(name, value) {
var checkRules = this.rules[name];
if (checkRules) {
checkRules(value);
}
this.settings[name] = value;
}
/**
* Return `setting`'s value.
*
* @param {String} setting
* @private
*/
}, {
key: 'get',
value: function get$$1(name) {
return this.settings[name];
}
}]);
return Settings;
}();
/**
* Module dependencies.
* @private
@ -747,9 +816,8 @@ var Application = function () {
classCallCheck(this, Application);
this.routers = [];
// this.isDOMLoaded = false;
// this.isDOMReady = false;
this.settings = new Settings();
this.plugins = [];
}
/**
@ -768,6 +836,8 @@ var Application = function () {
createClass(Application, [{
key: 'set',
value: function set$$1() {
var _settings;
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
@ -778,10 +848,7 @@ var Application = function () {
}
// set behaviour
var name = args[0],
value = args[1];
this.settings.set(name, value);
(_settings = this.settings).set.apply(_settings, args);
return this;
}
@ -807,6 +874,12 @@ var Application = function () {
value: function listen(callback) {
var _this = this;
var request = { method: 'GET', uri: window.location.pathname + window.location.search };
var response = { status: 200, statusText: 'OK' };
var currentRoutes = this._routes(request);
this._callMiddlewareMethod('entered', currentRoutes, request);
// manage history
window.onpopstate = function (event) {
if (event.state) {
@ -814,32 +887,27 @@ var Application = function () {
_request = _event$state.request,
_response = _event$state.response;
var _currentRoutes = _this._routes(_request.uri, _request.method);
_this._callMiddlewareMethod('exited');
_this._callMiddlewareMethod('entered', _currentRoutes, _request);
_this._callMiddlewareMethod('updated', _currentRoutes, _request, _response);
['exited', 'entered', 'updated'].forEach(function (middlewareMethod) {
return _this._callMiddlewareMethod(middlewareMethod, _this._routes(_request), _request, _response);
});
}
};
// manage page loading/refreshing
var request = { method: 'GET', uri: window.location.pathname + window.location.search };
var response = { status: 200, statusText: 'OK' };
var currentRoutes = this._routes();
window.onbeforeunload = function () {
_this._callMiddlewareMethod('exited');
};
var whenPageIsInteractiveFn = function whenPageIsInteractiveFn() {
_this.plugins.forEach(function (pluginObject) {
return pluginObject.plugin(_this);
});
_this._callMiddlewareMethod('updated', currentRoutes, request, response);
if (callback) {
callback(request, response);
}
};
window.onbeforeunload = function () {
_this._callMiddlewareMethod('exited');
};
this._callMiddlewareMethod('entered', currentRoutes, request);
document.onreadystatechange = function () {
// DOM ready state
if (document.readyState === 'interactive') {
@ -876,6 +944,7 @@ var Application = function () {
/**
* Use the given middleware function or object, with optional _uri_.
* Default _uri_ is "/".
* Or use the given plugin
*
* // middleware function will be applied on path "/"
* app.use((req, res, next) => {console.log('Hello')});
@ -883,8 +952,16 @@ var Application = function () {
* // middleware object will be applied on path "/"
* app.use(new Middleware());
*
* // use a plugin
* app.use({
* name: 'My plugin name',
* plugin(application) {
* // here plugin implementation
* }
* });
*
* @param {String} uri
* @param {Middleware|Function} middleware object or function
* @param {Middleware|Function|plugin} middleware object, middleware function, plugin
* @return {app} for chaining
*
* @public
@ -900,19 +977,24 @@ var Application = function () {
var _toParameters = toParameters(args),
baseUri = _toParameters.baseUri,
router = _toParameters.router,
middleware = _toParameters.middleware;
middleware = _toParameters.middleware,
plugin = _toParameters.plugin;
if (router) {
router.baseUri = baseUri;
} else if (middleware) {
router = new Router(baseUri);
HTTP_METHODS.forEach(function (method) {
router[method.toLowerCase()](middleware);
});
if (plugin) {
this.plugins.push(plugin);
} else {
throw new TypeError('method takes at least a middleware or a router');
if (router) {
router.baseUri = baseUri;
} else if (middleware) {
router = new Router(baseUri);
HTTP_METHODS.forEach(function (method) {
router[method.toLowerCase()](middleware);
});
} else {
throw new TypeError('method takes at least a middleware or a router');
}
this.routers.push(router);
}
this.routers.push(router);
return this;
}
@ -926,16 +1008,13 @@ var Application = function () {
}, {
key: '_routes',
value: function _routes() {
var uri = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.location.pathname + window.location.search;
var method = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'GET';
value: function _routes(request) {
var _this2 = this;
var currentRoutes = [];
this.routers.forEach(function (router) {
currentRoutes.push.apply(currentRoutes, toConsumableArray(router.routes(uri, method)));
});
return currentRoutes;
return this.routers.reduce(function (acc, router) {
acc.push.apply(acc, toConsumableArray(router.routes(_this2, request)));
return acc;
}, []);
}
/**
@ -995,7 +1074,7 @@ var Application = function () {
}, {
key: '_fetch',
value: function _fetch(req, resolve, reject) {
var _this2 = this;
var _this3 = this;
var method = req.method,
uri = req.uri,
@ -1019,7 +1098,7 @@ var Application = function () {
this._callMiddlewareMethod('exited');
// gathers all routes impacted by the uri
var currentRoutes = this._routes(uri, method);
var currentRoutes = this._routes(req);
// calls middleware entered method
this._callMiddlewareMethod('entered', currentRoutes, req);
@ -1029,12 +1108,12 @@ var Application = function () {
if (history) {
window.history.pushState({ request: request, response: response }, history.title, history.uri);
}
_this2._callMiddlewareMethod('updated', currentRoutes, request, response);
_this3._callMiddlewareMethod('updated', currentRoutes, request, response);
if (resolve) {
resolve(request, response);
}
}, function (request, response) {
_this2._callMiddlewareMethod('failed', currentRoutes, request, response);
_this3._callMiddlewareMethod('failed', currentRoutes, request, response);
if (reject) {
reject(request, response);
}
@ -1136,29 +1215,25 @@ HTTP_METHODS.reduce(function (reqProto, method) {
}, Application.prototype);
function toParameters(args) {
var _args, _args2, _args3, _args4;
var baseUri = void 0,
middleware = void 0,
router = void 0,
plugin = void 0,
which = void 0;
if (args && args.length > 0) {
if (args.length === 1) {
var _args = slicedToArray(args, 1);
which = _args[0];
} else {
var _args2 = slicedToArray(args, 2);
args.length === 1 ? (_args = args, _args2 = slicedToArray(_args, 1), which = _args2[0], _args) : (_args3 = args, _args4 = slicedToArray(_args3, 2), baseUri = _args4[0], which = _args4[1], _args3);
baseUri = _args2[0];
which = _args2[1];
}
if (which instanceof Router) {
router = which;
} else if (which instanceof Middleware || typeof which === 'function') {
middleware = which;
}
if (which instanceof Router) {
router = which;
} else if (which instanceof Middleware || typeof which === 'function') {
middleware = which;
} else if (which && which.plugin && typeof which.plugin === 'function') {
plugin = which;
}
return { baseUri: baseUri, middleware: middleware, router: router, which: which };
return { baseUri: baseUri, middleware: middleware, router: router, plugin: plugin, which: which };
}
/**

2
frontexpress.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -26,9 +26,8 @@ export default class Application {
constructor() {
this.routers = [];
// this.isDOMLoaded = false;
// this.isDOMReady = false;
this.settings = new Settings();
this.plugins = [];
}
@ -52,8 +51,7 @@ export default class Application {
}
// set behaviour
const [name, value] = args;
this.settings.set(name, value);
this.settings.set(...args);
return this;
}
@ -76,37 +74,37 @@ export default class Application {
*/
listen(callback) {
const request = {method: 'GET', uri: window.location.pathname + window.location.search};
const response = {status: 200, statusText: 'OK'};
const currentRoutes = this._routes(request);
this._callMiddlewareMethod('entered', currentRoutes, request);
// manage history
window.onpopstate = (event) => {
if (event.state) {
const {request, response} = event.state;
const currentRoutes = this._routes(request.uri, request.method);
this._callMiddlewareMethod('exited');
this._callMiddlewareMethod('entered', currentRoutes, request);
this._callMiddlewareMethod('updated', currentRoutes, request, response);
[
'exited',
'entered',
'updated'
].forEach(middlewareMethod => this._callMiddlewareMethod(middlewareMethod, this._routes(request), request, response));
}
};
// manage page loading/refreshing
const request = {method: 'GET', uri: window.location.pathname + window.location.search};
const response = {status: 200, statusText: 'OK'};
const currentRoutes = this._routes();
window.onbeforeunload = () => {
this._callMiddlewareMethod('exited');
};
const whenPageIsInteractiveFn = () => {
this.plugins.forEach(pluginObject => pluginObject.plugin(this));
this._callMiddlewareMethod('updated', currentRoutes, request, response);
if (callback) {
callback(request, response);
}
};
window.onbeforeunload = () => {
this._callMiddlewareMethod('exited');
};
this._callMiddlewareMethod('entered', currentRoutes, request);
document.onreadystatechange = () => {
// DOM ready state
if (document.readyState === 'interactive') {
@ -117,7 +115,6 @@ export default class Application {
if (['interactive', 'complete'].indexOf(document.readyState) !== -1) {
whenPageIsInteractiveFn();
}
}
@ -144,6 +141,7 @@ export default class Application {
/**
* Use the given middleware function or object, with optional _uri_.
* Default _uri_ is "/".
* Or use the given plugin
*
* // middleware function will be applied on path "/"
* app.use((req, res, next) => {console.log('Hello')});
@ -151,26 +149,38 @@ export default class Application {
* // middleware object will be applied on path "/"
* app.use(new Middleware());
*
* // use a plugin
* app.use({
* name: 'My plugin name',
* plugin(application) {
* // here plugin implementation
* }
* });
*
* @param {String} uri
* @param {Middleware|Function} middleware object or function
* @param {Middleware|Function|plugin} middleware object, middleware function, plugin
* @return {app} for chaining
*
* @public
*/
use(...args) {
let {baseUri, router, middleware} = toParameters(args);
if (router) {
router.baseUri = baseUri;
} else if (middleware) {
router = new Router(baseUri);
HTTP_METHODS.forEach((method) => {
router[method.toLowerCase()](middleware);
});
let {baseUri, router, middleware, plugin} = toParameters(args);
if (plugin) {
this.plugins.push(plugin);
} else {
throw new TypeError('method takes at least a middleware or a router');
if (router) {
router.baseUri = baseUri;
} else if (middleware) {
router = new Router(baseUri);
HTTP_METHODS.forEach((method) => {
router[method.toLowerCase()](middleware);
});
} else {
throw new TypeError('method takes at least a middleware or a router');
}
this.routers.push(router);
}
this.routers.push(router);
return this;
}
@ -183,13 +193,11 @@ export default class Application {
* @private
*/
_routes(uri=window.location.pathname + window.location.search, method='GET') {
const currentRoutes = [];
this.routers.forEach((router) => {
currentRoutes.push(...router.routes(uri, method));
});
return currentRoutes;
_routes(request) {
return this.routers.reduce((acc, router) => {
acc.push(...router.routes(this, request));
return acc;
}, []);
}
@ -261,7 +269,7 @@ export default class Application {
this._callMiddlewareMethod('exited');
// gathers all routes impacted by the uri
const currentRoutes = this._routes(uri, method);
const currentRoutes = this._routes(req);
// calls middleware entered method
this._callMiddlewareMethod('entered', currentRoutes, req);
@ -368,19 +376,17 @@ HTTP_METHODS.reduce((reqProto, method) => {
export function toParameters(args) {
let baseUri, middleware, router, which;
if (args && args.length > 0) {
if (args.length === 1) {
[which,] = args;
} else {
[baseUri, which,] = args;
}
let baseUri, middleware, router, plugin, which;
if (which instanceof Router) {
router = which;
} else if ((which instanceof Middleware) || (typeof which === 'function')) {
middleware = which;
}
args.length === 1 ? [which,] = args : [baseUri, which,] = args;
if (which instanceof Router) {
router = which;
} else if (which instanceof Middleware || typeof which === 'function') {
middleware = which;
} else if(which && which.plugin && typeof which.plugin === 'function') {
plugin = which;
}
return {baseUri, middleware, router, which};
return {baseUri, middleware, router, plugin, which};
}

View File

@ -67,3 +67,34 @@ export default class Requester {
}
}
}
export const httpGetTransformer = {
uri({uri, headers, data}) {
if (!data) {
return uri;
}
let [uriWithoutAnchor, anchor] = [uri, ''];
const match = /^(.*)(#.*)$/.exec(uri);
if (match) {
[,uriWithoutAnchor, anchor] = /^(.*)(#.*)$/.exec(uri);
}
uriWithoutAnchor = Object.keys(data).reduce((gUri, d, index) => {
gUri += `${(index === 0 && gUri.indexOf('?') === -1)?'?':'&'}${d}=${data[d]}`;
return gUri;
}, uriWithoutAnchor);
return uriWithoutAnchor + anchor;
}
};
// export const httpPostTransformer = {
// headers({uri, headers, data}) {
// if (!data) {
// return headers;
// }
// const updatedHeaders = headers || {};
// if (!updatedHeaders['Content-Type']) {
// updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
// }
// return updatedHeaders;
// }
// };

View File

@ -135,26 +135,11 @@ export default class Router {
* @private
*/
routes(uri, method) {
routes(application, request) {
request.params = request.params || {};
const isRouteMatch = application.get('route matcher');
return this._routes.filter((route) => {
if (route.method && route.method !== method) {
return false;
}
if (!route.uri || !uri) {
return true;
}
//remove query string from uri to test
//remove anchor from uri to test
const match = /^(.*)\?.*#.*|(.*)(?=\?|#)|(.*[^\?#])$/.exec(uri);
const baseUriToCheck = match[1] || match[2] || match[3];
if (route.uri instanceof RegExp) {
return baseUriToCheck.match(route.uri);
}
return route.uri === baseUriToCheck;
return isRouteMatch(request, route);
});
}
@ -267,3 +252,71 @@ HTTP_METHODS.forEach((method) => {
return this;
};
});
export function routeMatcher(request, route) {
// check if http method are equals
if (route.method && route.method !== request.method) {
return false;
}
// route and uri not defined always match
if (!route.uri || !request.uri) {
return true;
}
//remove query string and anchor from uri to test
const match = /^(.*)\?.*#.*|(.*)(?=\?|#)|(.*[^\?#])$/.exec(request.uri);
const baseUriToCheck = match[1] || match[2] || match[3];
// if route is a regexp path
if (route.uri instanceof RegExp) {
return baseUriToCheck.match(route.uri) !== null;
}
// if route is parameterized path
if (route.uri.indexOf(':') !== -1) {
const decodeParmeterValue = (v) => {
return !isNaN(parseFloat(v)) && isFinite(v) ? (Number.isInteger(v) ? Number.parseInt(v, 10) : Number.parseFloat(v)) : v;
};
// figure out key names
const keys = [];
const keysRE = /:([^\/\?]+)\??/g;
let keysMatch = keysRE.exec(route.uri);
while (keysMatch != null) {
keys.push(keysMatch[1]);
keysMatch = keysRE.exec(route.uri);
}
// change parameterized path to regexp
const regExpUri = route.uri
// :parameter?
.replace(/\/:[^\/]+\?/g, '(?:\/([^\/]+))?')
// :parameter
.replace(/:[^\/]+/g, '([^\/]+)')
// escape all /
.replace('/', '\\/');
// checks if uri match
const routeMatch = baseUriToCheck.match(new RegExp(`^${regExpUri}$`));
if (!routeMatch) {
return false;
}
// update params in request with keys
request.params = Object.assign(request.params, keys.reduce((acc, key, index) => {
let value = routeMatch[index + 1];
if (value) {
value = value.indexOf(',') !== -1 ? value.split(',').map(v => decodeParmeterValue(v)) : value = decodeParmeterValue(value);
}
acc[key] = value;
return acc;
}, {}));
return true;
}
// if route is a simple path
return route.uri === baseUriToCheck;
}

View File

@ -2,9 +2,15 @@
* Module dependencies.
* @private
*/
import {routeMatcher} from './router';
import Requester, {httpGetTransformer} from './requester';
import Requester from './requester';
function errorIfNotFunction(toTest, message) {
if(typeof toTest !== 'function') {
throw new TypeError(message);
}
}
/**
* Settings object.
@ -26,43 +32,22 @@ export default class Settings {
// default settings
this.settings = {
'http requester': new Requester(),
'http GET transformer': {
uri({uri, headers, data}) {
if (!data) {
return uri;
}
let [uriWithoutAnchor, anchor] = [uri, ''];
const match = /^(.*)(#.*)$/.exec(uri);
if (match) {
[,uriWithoutAnchor, anchor] = /^(.*)(#.*)$/.exec(uri);
}
uriWithoutAnchor = Object.keys(data).reduce((gUri, d, index) => {
gUri += `${(index === 0 && gUri.indexOf('?') === -1)?'?':'&'}${d}=${data[d]}`;
return gUri;
}, uriWithoutAnchor);
return uriWithoutAnchor + anchor;
}
}
// 'http POST transformer': {
// headers({uri, headers, data}) {
// if (!data) {
// return headers;
// }
// const updatedHeaders = headers || {};
// if (!updatedHeaders['Content-Type']) {
// updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
// }
// return updatedHeaders;
// }
// }
'http GET transformer': httpGetTransformer,
// 'http POST transformer': httpPostTransformer,
'route matcher': routeMatcher
};
this.rules = {
'http requester': (requester) => {
if(typeof requester.fetch !== 'function') {
throw new TypeError('setting http requester has no fetch method');
errorIfNotFunction(requester.fetch , 'setting http requester has no fetch function');
},
'http GET transformer': (transformer) => {
if (!transformer || (!transformer.uri && !transformer.headers && !transformer.data)) {
throw new TypeError('setting http transformer one of functions: uri, headers, data is missing');
}
},
'route matcher': (routeMatcher) => {
errorIfNotFunction(routeMatcher, 'setting route matcher is not a function');
}
};
}

View File

@ -1,6 +1,6 @@
{
"name": "frontexpress",
"version": "1.1.0",
"version": "1.2.0",
"description": "Frontexpress manages routes in browser like ExpressJS on Node",
"main": "dist/frontexpress.js",
"jsnext:main": "lib/frontexpress.js",

View File

@ -989,4 +989,22 @@ describe('Application', () => {
});
});
});
describe('plugin management', () => {
it('setup a plugin', (done) => {
const app = frontexpress();
app.use({
name: 'my plugin',
plugin(application) {
done();
}
});
app.listen();
//simulate readystatechange
document.readyState = 'interactive';
document.onreadystatechange();
});
});
});

20
test/middleware-test.js Normal file
View File

@ -0,0 +1,20 @@
/*eslint-env mocha*/
import {assert} from 'chai';
import Middleware from '../lib/middleware';
describe('Middleware', () => {
it('check exposed methods', () => {
const middleware = new Middleware();
assert(middleware.entered);
assert(middleware.exited);
assert(middleware.updated);
assert(middleware.failed);
assert(middleware.next);
middleware.entered();
middleware.exited();
middleware.updated();
middleware.failed();
assert(middleware.next());
});
});

View File

@ -4,6 +4,9 @@ import sinon from 'sinon';
import frontexpress from '../lib/frontexpress';
import HTTP_METHODS from '../lib/methods';
const application = frontexpress();
const routeMatcher = application.get('route matcher');
describe('Router', () => {
describe('generated methods', () => {
@ -12,6 +15,7 @@ describe('Router', () => {
assert(typeof router.all === 'function');
assert(typeof router.get === 'function');
assert(typeof router.put === 'function');
assert(typeof router.patch === 'function');
assert(typeof router.post === 'function');
assert(typeof router.delete === 'function');
});
@ -46,7 +50,7 @@ describe('Router', () => {
router.get(middleware);
const r1 = router.routes('/', 'GET');
const r1 = router.routes(application, {uri: '/', method: 'GET'});
assert(r1.length === 1);
assert(r1[0].uri === undefined);
assert(r1[0].method === 'GET');
@ -64,37 +68,37 @@ describe('Router', () => {
.post('/route2', middleware2)
.all('/route3', middleware3);
const r1 = router.routes('/route1', 'GET');
const r1 = router.routes(application, {uri: '/route1', method: 'GET'});
assert(r1.length === 1);
assert(r1[0].uri === '/route1');
assert(r1[0].method === 'GET');
assert(r1[0].middleware === middleware1);
const r2 = router.routes('/route2', 'POST');
const r2 = router.routes(application, {uri: '/route2', method: 'POST'});
assert(r2.length === 1);
assert(r2[0].uri === '/route2');
assert(r2[0].method === 'POST');
assert(r2[0].middleware === middleware2);
let r3 = router.routes('/route3', 'GET');
let r3 = router.routes(application, {uri: '/route3', method: 'GET'});
assert(r3.length === 1);
assert(r3[0].uri === '/route3');
assert(r3[0].method === 'GET');
assert(r3[0].middleware === middleware3);
r3 = router.routes('/route3', 'POST');
r3 = router.routes(application, {uri: '/route3', method: 'POST'});
assert(r3.length === 1);
assert(r3[0].uri === '/route3');
assert(r3[0].method === 'POST');
assert(r3[0].middleware === middleware3);
r3 = router.routes('/route3', 'PUT');
r3 = router.routes(application, {uri: '/route3', method: 'PUT'});
assert(r3.length === 1);
assert(r3[0].uri === '/route3');
assert(r3[0].method === 'PUT');
assert(r3[0].middleware === middleware3);
r3 = router.routes('/route3', 'DELETE');
r3 = router.routes(application, {uri: '/route3', method: 'DELETE'});
assert(r3.length === 1);
assert(r3[0].uri === '/route3');
assert(r3[0].method === 'DELETE');
@ -107,7 +111,7 @@ describe('Router', () => {
router.get(/^\/route1/, middleware);
const r = router.routes('/route1', 'GET');
const r = router.routes(application, {uri: '/route1', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri instanceof RegExp);
assert(r[0].uri.toString() === new RegExp('^\/route1').toString());
@ -120,7 +124,7 @@ describe('Router', () => {
router.get('/subroute', new frontexpress.Middleware());
const r = router.routes('/route1/subroute', 'GET');
const r = router.routes(application, {uri: '/route1/subroute', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
});
@ -130,7 +134,7 @@ describe('Router', () => {
router.get(new frontexpress.Middleware());
const r = router.routes('/route1', 'GET');
const r = router.routes(application, {uri: '/route1', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1');
});
@ -140,7 +144,7 @@ describe('Router', () => {
router.get('/subroute', new frontexpress.Middleware());
const r = router.routes('/route1/subroute', 'GET');
const r = router.routes(application, {uri: '/route1/subroute', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
});
@ -150,7 +154,7 @@ describe('Router', () => {
router.get('/subroute ', new frontexpress.Middleware());
let r = router.routes('/route1/subroute', 'GET');
let r = router.routes(application, {uri: '/route1/subroute', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
@ -160,7 +164,7 @@ describe('Router', () => {
router.get(new frontexpress.Middleware());
r = router.routes('/route1', 'GET');
r = router.routes(application, {uri: '/route1', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1');
});
@ -170,7 +174,7 @@ describe('Router', () => {
router.get('/subroute', new frontexpress.Middleware());
let r = router.routes('/route1/subroute?a=b&c=d', 'GET');
let r = router.routes(application, {uri: '/route1/subroute?a=b&c=d', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
assert(r[0].data === undefined);
@ -181,7 +185,7 @@ describe('Router', () => {
router.get('/subroute', new frontexpress.Middleware());
let r = router.routes('/route1/subroute#a=b&c=d', 'GET');
let r = router.routes(application, {uri: '/route1/subroute#a=b&c=d', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
assert(r[0].data === undefined);
@ -192,7 +196,7 @@ describe('Router', () => {
router.get('/subroute', new frontexpress.Middleware());
let r = router.routes('/route1/subroute?a=b&c=d#anchor1', 'GET');
let r = router.routes(application, {uri: '/route1/subroute?a=b&c=d#anchor1', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
assert(r[0].data === undefined);
@ -264,7 +268,7 @@ describe('Router', () => {
router.get(middleware);
const r = router.routes('/', 'GET');
const r = router.routes(application, {uri: '/', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/');
assert(r[0].method === 'GET');
@ -277,7 +281,7 @@ describe('Router', () => {
router.get('/route1', middleware);
const r = router.routes('/route1', 'GET');
const r = router.routes(application, {uri: '/route1', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1');
assert(r[0].method === 'GET');
@ -295,7 +299,7 @@ describe('Router', () => {
const middleware = new frontexpress.Middleware();
router.get(middleware);
const r = router.routes('/part1', 'GET');
const r = router.routes(application, {uri: '/part1', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri instanceof RegExp);
assert(r[0].uri.toString() === new RegExp('^\/part').toString());
@ -303,4 +307,113 @@ describe('Router', () => {
assert(r[0].middleware === middleware);
});
});
describe('check route matcher', () => {
it('/', () => {
const route = {uri: '/', method: 'GET'};
const request = {uri: '/', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {});
});
it('/a/b/c', () => {
const route = {uri: '/a/b/c', method: 'GET'};
let request = {uri: '/a/b/c', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {});
request = {uri: '/a/b/c/', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
});
it('/^\//', () => {
const route = {uri: /^\//, method: 'GET'};
const request = {uri: '/a/b/c', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {});
});
it('/:id', () => {
const route = {uri: '/:id', method: 'GET'};
const request = {uri: '/1000', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.strictEqual(request.params.id, 1000);
});
it('/user/:id', () => {
const route = {uri: '/user/:id', method: 'GET'};
let request = {uri: '/user/1000', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.strictEqual(request.params.id, 1000);
request = {uri: '/user/100.2122', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.strictEqual(request.params.id, 100.2122);
request = {uri: '/user', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
request = {uri: '/user/', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
});
it('/user/:id with id as coma separated values', () => {
const route = {uri: '/user/:id', method: 'GET'};
let request = {uri: '/user/1,2,3', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {id: [1,2,3]});
request = {uri: '/user/1.5,2.55,4.25', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {id: [1.5,2.55,4.25]});
request = {uri: '/user/a,b,c', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {id: ['a','b','c']});
});
it('/user/:id?', () => {
const route = {uri: '/user/:id?', method: 'GET'};
let request = {uri: '/user/1000', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.strictEqual(request.params.id, 1000);
request = {uri: '/user', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), true);
assert.deepEqual(request.params, {id: undefined});
request = {uri: '/user/', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
});
it('/user/:firstname/:lastname', () => {
const route = {uri: '/user/:firstname/:lastname', method: 'GET'};
let request = {uri: '/user/camel/aissani', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {firstname: 'camel', lastname:'aissani'} );
request = {uri: '/user/camel', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
});
it('/user/:firstname?/:lastname', () => {
const route = {uri: '/user/:firstname?/:lastname', method: 'GET'};
let request = {uri: '/user/camel/aissani', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {firstname: 'camel', lastname:'aissani'} );
request = {uri: '/user/aissani', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {firstname: undefined, lastname:'aissani'} );
});
});
});

View File

@ -1,11 +1,43 @@
/*eslint-env mocha*/
import {assert} from 'chai';
import chai, {assert} from 'chai';
import Settings from '../lib/settings';
describe('Settings', () => {
const settings = new Settings();
describe('http GET method transformer', () => {
it('check setting rule', () => {
const defaultHttpGetTransformer = settings.get('http GET transformer');
chai.expect(() => settings.set('http GET transformer', null)).to.throw(TypeError);
chai.expect(() => settings.set('http GET transformer', {})).to.throw(TypeError);
chai.expect(() => settings.set('http GET transformer', {foo:()=>{}})).to.throw(TypeError);
const uri = () => {};
settings.set('http GET transformer', {uri});
assert.deepEqual(settings.get('http GET transformer'), {uri});
const headers = () => {};
settings.set('http GET transformer', {headers});
assert.deepEqual(settings.get('http GET transformer'), {headers});
const data = () => {};
settings.set('http GET transformer', {data});
assert.deepEqual(settings.get('http GET transformer'), {data});
settings.set('http GET transformer', defaultHttpGetTransformer);
const defaultRouteMatcher = settings.get('route matcher');
chai.expect(() => settings.set('route matcher', null)).to.throw(TypeError);
chai.expect(() => settings.set('route matcher', {})).to.throw(TypeError);
chai.expect(() => settings.set('route matcher', 1)).to.throw(TypeError);
const routeMatcher = () => {};
settings.set('route matcher', routeMatcher);
assert.strictEqual(settings.get('route matcher'), routeMatcher);
});
it('simple uri', () => {
const uriFn = settings.get('http GET transformer').uri;
const dataFn = settings.get('http GET transformer').data;