Compare commits

...

69 Commits

Author SHA1 Message Date
Camel Aissani
acebd1e5c0 typo 2017-06-24 10:02:06 +02:00
Camel Aissani
d678d066c3 Update README.md 2017-06-24 09:53:59 +02:00
Camel Aissani
2245de31d7 Update README.md 2017-06-23 19:43:04 +02:00
Camel Aissani
065b500561 Merge branch 'master' of github.com:camelaissani/frontexpress 2017-06-23 12:34:50 +02:00
Camel Aissani
75b130ef91 clean/update package.json file 2017-06-23 12:34:29 +02:00
Camel Aissani
72598f09e1 Update README.md 2017-06-23 10:21:56 +02:00
Camel Aissani
d7cfd6d9f3 prepare new release 2017-06-22 21:28:31 +02:00
Camel Aissani
21f5dea449 Update README.md 2017-06-22 21:22:12 +02:00
Camel Aissani
a1e56dc6c3 fixed issues #5 #6 2017-06-22 21:17:18 +02:00
Camel Aissani
34b7580d18 Updated link to jsDelivr 2017-06-15 20:28:29 +02:00
Camel Aissani
4ab3fa2626 added PATCH http method - Fixed middleware callbacks 2017-06-14 22:47:59 +02:00
Camel Aissani
5b45250bf5 added frontexpress.js.min in package 2017-05-26 14:50:58 +02:00
Camel Aissani
787a22ed44 prepared new patch version 2017-05-26 14:27:12 +02:00
Camel Aissani
79062fe337 better support of es modules 2017-05-26 14:25:37 +02:00
Camel Aissani
f90743d4cf Update README.md 2017-01-16 08:23:22 +01:00
Camel Aissani
5620117e6c Update README.md 2017-01-15 14:33:30 +01:00
Camel Aissani
b22725acd0 refactored code for reducing lib size 2017-01-15 12:00:51 +01:00
Camel Aissani
abc9ffc405 fixed regression on router.baseUri 2017-01-14 16:08:22 +01:00
Camel Aissani
a9b90040bd diet for reducing lib size 2017-01-14 15:02:50 +01:00
Camel Aissani
41b4a5ed19 Update README.md 2017-01-13 22:08:48 +01:00
Camel Aissani
29f07a7259 Update README.md 2017-01-13 15:18:54 +01:00
Camel Aissani
c6fa4e2919 Update README.md 2017-01-13 15:10:12 +01:00
Camel Aissani
3fad8330f6 Update frontexpress.md 2017-01-13 15:03:41 +01:00
Camel Aissani
03c6284aa8 Update README.md 2017-01-13 15:02:45 +01:00
Camel Aissani
316e8d1ac0 Update README.md 2017-01-13 14:59:20 +01:00
Camel Aissani
c35bb0dd69 Update README.md 2017-01-13 14:58:08 +01:00
Camel Aissani
92cae2c0a4 added codeclimate config 2017-01-13 14:47:30 +01:00
Camel Aissani
b2ab39b942 added codeclimate config 2017-01-13 14:38:38 +01:00
Camel Aissani
02b3fc1edb optimized lib size - changed frontexpress.Middleware now it returns a class and not an instance anymore 2017-01-13 14:23:15 +01:00
Camel Aissani
465c53cd4f attempt to fix build (missing dev dependency) 2016-09-15 19:25:46 +02:00
Camel Aissani
a170883d93 replaced webpack by rollup to reduce browser bundle size (-3kb) 2016-09-15 19:16:42 +02:00
Camel Aissani
12621d0883 Updated according request issue #3 2016-09-14 00:24:23 +02:00
Camel Aissani
ff83e279f1 Update README.md 2016-09-14 00:20:30 +02:00
Camel Aissani
00f5a9d1cc updated version 2016-09-12 00:43:04 +02:00
Camel Aissani
6659ca98b7 added source map and made lib directory visible for bower deployment #3 2016-09-12 00:40:45 +02:00
Camel Aissani
2702bb8ff6 shrank frontexpress.min.js - added frontexpress.js - added those files in .npmignore #2 2016-09-09 23:00:43 +02:00
Camel Aissani
474274c71f exposed frontexpress in the browser global context #2 2016-09-08 23:19:07 +02:00
Camel Aissani
ef16262c24 added frontexpress.min.js in .eslintignore 2016-09-08 21:52:28 +02:00
Camel Aissani
0949461127 Added webpack to generate frontexpress.min.js for bower deployment #2 2016-09-08 21:50:01 +02:00
Camel Aissani
c7310adf2a Merge branch 'master' of github.com:camelaissani/frontexpress 2016-09-08 20:32:18 +02:00
Camel Aissani
9fc6a6fb2b Publish to bower as well! #2 2016-09-08 20:31:45 +02:00
Camel Aissani
ab2feb1c30 Publish to bower as well! #2 2016-09-08 20:28:54 +02:00
Camel Aissani
009ff8fdb9 Update README.md 2016-08-28 10:56:05 +02:00
Camel Aissani
fc3f7e1d7d Prepared version 0.1.5 2016-07-26 21:24:15 +02:00
Camel Aissani
b463c05733 Update README.md 2016-07-26 21:17:56 +02:00
Camel Aissani
27da6f94bf Update README.md 2016-07-26 21:16:34 +02:00
Camel Aissani
893d39e765 Update README.md 2016-07-26 21:14:38 +02:00
Camel Aissani
8dfd3731fd Update README.md 2016-07-26 21:14:05 +02:00
Camel Aissani
1d10bfe2fa Update README.md 2016-07-26 21:05:27 +02:00
Camel Aissani
a5da97c3bd New version 2016-07-23 14:48:28 +02:00
Camel Aissani
4cc0a9f511 Merge branch 'master' of github.com:camelaissani/frontexpress 2016-07-23 14:41:50 +02:00
Camel Aissani
79ac822adb replaced main sample in README 2016-07-23 14:41:37 +02:00
Camel Aissani
089098a94f Fixed typo 2016-07-23 13:37:09 +02:00
Camel Aissani
f15cfed2e9 Refactored browser history management 2016-07-23 12:31:40 +02:00
Camel Aissani
5d75c4a259 completed README 2016-07-23 11:46:03 +02:00
Camel Aissani
dd1d972f57 completed README 2016-07-23 11:35:39 +02:00
Camel Aissani
965dd5f08d fixed some markdown 2016-07-23 10:57:41 +02:00
Camel Aissani
b820c2c6d0 fixed some jsdoc and added some markdown 2016-07-23 10:48:26 +02:00
Camel Aissani
e17d46ad20 Update README.md 2016-07-23 01:12:14 +02:00
Camel Aissani
a8edd83f86 Update frontexpress.md 2016-07-23 00:52:49 +02:00
Camel Aissani
74bcccec72 Update README.md 2016-07-23 00:52:13 +02:00
Camel Aissani
be20f98498 added docs 2016-07-23 00:02:34 +02:00
Camel Aissani
2bcf695198 update version 2016-07-17 14:01:16 +02:00
Camel Aissani
390cd54a48 Added browser history management issue #1 2016-07-17 13:37:35 +02:00
Camel Aissani
9c7681e2b4 Update README.md 2016-07-17 11:17:08 +02:00
Camel Aissani
534f529b0c updated README.md 2016-07-17 11:14:13 +02:00
Camel Aissani
9223011ebe updated devDependencies 2016-07-17 11:05:54 +02:00
Camel Aissani
c42133a421 added minimum version of node 2016-07-15 21:36:32 +02:00
Camel Aissani
6d2dd79194 added jsdoc 2016-07-15 21:34:24 +02:00
33 changed files with 3243 additions and 582 deletions

9
.codeclimate.yml Normal file
View File

@ -0,0 +1,9 @@
engines:
duplication:
enabled: true
config:
languages:
- javascript
ratings:
paths:
- "lib/**/*"

View File

@ -1,2 +1,5 @@
frontexpress.js
frontexpress.min.js
frontexpress.min.js.map
coverage coverage
dist dist

View File

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

View File

@ -6,4 +6,4 @@ before_script:
- npm install coveralls - npm install coveralls
after_script: after_script:
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js

405
README.md
View File

@ -1,41 +1,272 @@
![frontexpress](http://fontmeme.com/embed.php?text=frontexpress&name=Atype%201%20Light.ttf&size=90&style_color=6F6F75) ![frontexpress](http://fontmeme.com/embed.php?text=frontexpress&name=Atype%201%20Light.ttf&size=90&style_color=6F6F75)
Minimalist front end router framework à la [express](http://expressjs.com/) A simple vanilla JavaScript router a la [ExpressJS](http://expressjs.com/).
Code the front-end like the back-end.
[frontexpress demo](https://github.com/camelaissani/frontexpress-demo)
[![Build Status](https://travis-ci.org/camelaissani/frontexpress.svg?branch=master)](https://travis-ci.org/camelaissani/frontexpress) [![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) [![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) [![Coverage Status](https://coveralls.io/repos/github/camelaissani/frontexpress/badge.svg?branch=master)](https://coveralls.io/github/camelaissani/frontexpress?branch=master)
![Coverage Status](https://david-dm.org/camelaissani/frontexpress.svg) ![dependencies](https://img.shields.io/gemnasium/mathiasbynens/he.svg)
![Size Shield](https://img.shields.io/badge/size-3.26kb-brightgreen.svg)
```js
import frontexpress from 'frontexpress';
const app = frontexpress();
// listen HTTP GET request on path (/)
app.get('/', (req, res) => {
window.alert('Hello World');
});
// listen HTTP GET request on API (/api/xxx)
// update page content with response
app.get(/^\/api\//, (req, res, next) => {
document.querySelector('.content').innerHTML = res.responseText;
next();
});
// start listening frontend application requests
app.listen();
```
## Installation ## Installation
### From npm repository
```bash ```bash
$ npm install frontexpress $ npm install frontexpress
``` ```
## Quick Start ### From bower repository
The quickest way to get started with frontexpress is to clone the [frontexpress-demo](https://github.com/camelaissani/frontexpress-demo) repository. ```bash
$ bower install frontexpress
```
### From CDN
On [jsDelivr](https://cdn.jsdelivr.net/npm/frontexpress@1.2.0/frontexpress.min.js)
## Usage
```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 = res.responseText;
});
// front-end logic on navigation path "/page2"
app.get('/page2', (req, res) => {
document.querySelector('.content').innerHTML = res.responseText;
});
// start front-end application
app.listen();
```
### Routes
Listen GET requests on path /hello:
```js
app.get('/hello', (req, res) => {
window.alert('Hello World');
});
```
Listen POST requests on path /item:
```js
app.post('/item', (req, res) => {
window.alert('Got a POST request at /item');
});
```
Listen GET requests on path starting with /api/:
```js
app.get(/^api\//, (req, res) => {
console.log(`api was requested ${req.uri}`);
});
```
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]
});
```
You can have the full capabilities of Express-style path with this plugin [frontexpress-path-to-regexp](https://github.com/camelaissani/frontexpress-path-to-regexp)
### 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.
At the opposite, when the ```next()``` method is not called the handler chain is stopped.
```js
const h1 = (req, res, next) => { console.log('h1!'); next(); };
const h2 = (req, res, next) => { console.log('h2!') };
const h3 = (req, res, next) => { console.log('h3!'); next(); };
app.get('/example/a', h1);
app.get('/example/a', h2);
app.get('/example/a', h3);
```
On navigation on path /example/a, the browser console displays the following:
```
h1!
h2!
```
h3 is ignored because ```next()``` function was not invoked.
#### app.route()
You can create chainable route handlers for a route path by using ```app.route()```.
```js
app.route('/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
Use the ```frontexpress.Router``` class to create modular, mountable route handlers.
Create a router file named ```birds.js```:
```js
import frontexpress from 'frontexpress';
const router = frontexpress.Router();
// specific middleware for this router
router.use((req, res, next) => {
console.log(`Time: ${Date.now()}`);
next();
});
// listen navigation on the home page
router.get('/', (req, res) => {
document.querySelector('.content').innerHTML = '<p>Birds home page</p>';
});
// listen navigation on the about page
router.get('/about', (req, res) => {
document.querySelector('.content').innerHTML = '<p>About birds</p>';
});
export default router;
```
Then, load the router module in the app:
```js
import birds from './birds';
...
app.use('/birds', birds);
```
## Plugins
### Extend frontexpress via plugins:
- [frontexpress-path-to-regexp](https://github.com/camelaissani/frontexpress-path-to-regexp): Add the ability to support Express-style path string such as /user/:name, /user*...
Others are coming
### Write your own plugin
It consists to simply create an object with two properties:
- **name**: the name of your plugin
- **plugin**: the function containing the implementation
Let's assume that we have implemented this plugin in the `frontexpress-my-plugin.js` file as below:
```js
export default {
name: 'My plugin',
plugin(app) {
// the plugin implementation goes here
// Some ideas
// you can get settings
// const transformer = app.get('http GET transformer');
//
// you can set settings
// app.set('http requester', {
// fetch() {
// ...
// }});
//
// you can complete routes
// app.get(...)
}
};
```
To use it:
```js
import frontexpress from 'frontexpress';
import myPlugin from 'frontexpress-my-plugin';
// Front-end application
const app = frontexpress();
// tell to frontexpress to use your plugin
app.use(myPlugin);
```
## More
[API](https://github.com/camelaissani/frontexpress/blob/master/docs/api.md)
## Tests ## Tests
@ -53,130 +284,6 @@ $ npm install
$ npm test $ npm test
``` ```
## Disclaimer
>
> In this first version of frontexpress, the API is not completely the miror of the expressjs one.
>
> There are some missing methods. Currently, the use, get, post... methods having a middlewares 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.
>
## Routing
### Basic routing
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');
});
```
Listen an HTTP POST request on URI (/):
```js
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) => {
console.log(`api was requested ${req.uri}`);
});
```
### Route handlers
You can provide multiple callback functions to handle a request. Invoking ```next()``` function allows to pass the control to subsequent routes.
Whether ```next()``` method is not called the handler chain is stoped
```js
const h1 = (req, res, next) => { console.log('h1!'); next(); };
const h2 = (req, res, next) => { console.log('h2!') };
const h3 = (req, res, next) => { console.log('h3!'); next(); };
app.get('/example/a', h1);
app.get('/example/a', h2);
app.get('/example/a', h3);
```
A response to a GET request on path (/example/a) displays:
```
h1!
h2!
```
h3 is ignored because ```next()``` function was not invoked.
### app.route()
You can create chainable route handlers for a route path by using ```app.route()```.
```js
app.route('/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
Use the ```frontexpress.Router``` class to create modular, mountable route handlers.
Create a router file named ```birds.js``` in the app directory, with the following content:
```js
import frontexpress from 'frontexpress';
const router = frontexpress.Router();
// middleware that is specific to this router
router.use((req, res, next) => {
console.log(`Time: ${Date.now()}`);
next();
});
// 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>';
});
export default router;
```
Then, load the router module in the app:
```js
import birds from './birds';
...
app.use('/birds', birds);
```
The app will now be able to react on requests (/birds) and (/birds/about)
## License ## License
[MIT](LICENSE) [MIT](LICENSE)

33
bower.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "frontexpress",
"description": "Frontexpress manages routes in browser like ExpressJS on Node",
"main": "dist/frontexpress.js",
"authors": [
"Camel Aissani <camel.aissani@gmail.com> (https://nuageprive.fr)"
],
"license": "MIT",
"keywords": [
"front",
"framework",
"web",
"router",
"middleware",
"app",
"api",
"express",
"frontexpress"
],
"homepage": "https://github.com/camelaissani/frontexpress",
"ignore": [
"**/.*",
"index.js",
"gzipsize.js",
"rollup.config.dev.js",
"rollup.config.prod.js",
"node_modules",
"bower_components",
"test",
"tests",
"coverage"
]
}

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)

133
docs/application.md Normal file
View File

@ -0,0 +1,133 @@
# Application
## Application.set(setting, val)
Assign `setting` to `val`, or return `setting`'s value.
```js
app.set('foo', 'bar');
app.set('foo');
// => "bar"
```
**Parameters**
**setting**: `String`, setting name
**val**: `*`, setting value
**Returns**: `app`, for chaining
## Application.listen(callback)
Listen to the DOM initialization and the browser history state changes.
The callback function is called once the DOM has
the `document.readyState` equals to 'interactive'.
```js
app.listen(()=> {
console.log('App is listening requests');
console.log('DOM is ready!');
});
```
**Parameters**
**callback**: `function`, DOM is ready callback
## Application.route(uri)
Create a new `Router` instance for the _uri_.
See the Router api docs for details.
```js
app.route('/');
// => new Router instance
```
**Parameters**
**uri**: `String`, path
**Returns**: `Router`, for chaining
## Application.use(uri, middleware)
Use the given middleware function or object, with optional _uri_.
Default _uri_ is "/".
```js
// middleware function will be applied on path "/"
app.use((req, res, next) => {console.log('Hello')});
// middleware object will be applied on path "/"
app.use(new Middleware());
```
**Parameters**
**uri**: `String`, path
**middleware**: `Middleware | function`, Middleware object or function
**Returns**: `app`, for chaining
## Application.get(uri, middleware), Application.post(uri, middleware)...
Use the given middleware function or object, with optional _uri_ on
HTTP methods: get, post, put, delete...
Default _uri_ is "/".
```js
// middleware function will be applied on path "/"
app.get((req, res, next) => {console.log('Hello')});
// middleware object will be applied on path "/" and
app.get(new Middleware());
// get a setting value
app.set('foo', 'bar');
app.get('foo');
// => "bar"
```
**Parameters**
**uri**: `String`, path (or setting only for get method)
**middleware**: `Middleware | function`, Middleware object or function
**Returns**: `app`, for chaining
## Application.httpGet(request, success, failure), Application.httpPost(request, success, failure)...
Make an ajax request (get, post, put, delete...).
```js
// HTTP GET method
httpGet('/route1');
// HTTP GET method
httpGet({uri: '/route1', data: {'p1': 'val1'});
// uri invoked => /route1?p1=val1
// HTTP GET method with browser history management
httpGet({uri: '/api/users', history: {state: {foo: "bar"}, title: 'users page', uri: '/view/users'});
```
Samples above can be applied on other HTTP methods.
**Parameters**
**request**: `String | Object` uri or object containing uri, http headers, data, history
**success**: `Function` success callback
**failure**: `Function` failure callback

22
docs/frontexpress.md Normal file
View File

@ -0,0 +1,22 @@
# Frontexpress
## frontexpress()
Create a frontexpress application.
**Returns**: `Application`, the application
## frontexpress.Router()
Expose the Router constructor
**Returns**: `Router`
## frontexpress.Middleware
Expose the Middleware class
**Returns**: `Middleware`

63
docs/middleware.md Normal file
View File

@ -0,0 +1,63 @@
# Middleware
## Middleware.entered(request)
Invoked by the app before ajax request are sent or
during the DOM loading (document.readyState === 'loading').
See Application#_callMiddlewareEntered documentation for details.
Override this method to add your custom behaviour
**Parameters**
**request**: `Object`
## Middleware.exited(request)
Invoked by the app before a new ajax request is sent or before the DOM unloading.
See Application#_callMiddlewareExited documentation for details.
Override this method to add your custom behaviour
**Parameters**
**request**: `Object`
## Middleware.updated(request, response)
Invoked on ajax request responding or on DOM ready
(document.readyState === 'interactive').
See Application#_callMiddlewareUpdated documentation for details.
Override this method to add your custom behaviour
**Parameters**
**request**: `Object`
**response**: `Object`
## Middleware.failed(request, response)
Invoked when ajax request fails.
Override this method to add your custom behaviour
**Parameters**
**request**: `Object`
**response**: `Object`
## Middleware.next()
Allow the hand over to the next middleware object or function.
Override this method and return `false` to break execution of
middleware chain.
**Returns**: `Boolean`, `true` by default

69
docs/router.md Normal file
View File

@ -0,0 +1,69 @@
# Router
## Router.use(middleware)
Use the given middleware function or object on this router.
```js
// middleware function
router.use((req, res, next) => {console.log('Hello')});
// middleware object
router.use(new Middleware());
```
**Parameters**
**middleware**: `Middleware | function`, Middleware object or function
**Returns**: `Router`, for chaining
## Router.all(middleware)
Use the given middleware function or object on this router for
all HTTP methods.
```js
// middleware function
router.all((req, res, next) => {console.log('Hello')});
// middleware object
router.all(new Middleware());
```
**Parameters**
**middleware**: `Middleware | function`, Middleware object or function
**Returns**: `Router`, for chaining
## Router.get(uri, middleware), Router.post(uri, middleware)...
Use the given middleware function or object, with optional _uri_ on
HTTP methods: get, post, put, delete...
Default _uri_ is "/".
```js
// middleware function will be applied on path "/"
router.get((req, res, next) => {console.log('Hello')});
// middleware object will be applied on path "/" and
router.get(new Middleware());
// middleware function will be applied on path "/user"
router.post('/user', (req, res, next) => {console.log('Hello')});
// middleware object will be applied on path "/user" and
router.post('/user', new Middleware());
```
**Parameters**
**uri**: `String`, path
**middleware**: `Middleware | function`, Middleware object or function
**Returns**: `Router`, for chaining

1264
frontexpress.js Normal file

File diff suppressed because it is too large Load Diff

2
frontexpress.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
frontexpress.min.js.map Normal file

File diff suppressed because one or more lines are too long

5
gzipsize.js Normal file
View File

@ -0,0 +1,5 @@
import bytesize from 'bytesize';
bytesize.gzipSize(__dirname + '/frontexpress.min.js', true, (err, size) => {
console.log(`frontexpress size: ${size}(min+gzip)`);
});

1
index.js Normal file
View File

@ -0,0 +1 @@
import frontexpress from 'expose?frontexpress!./lib/frontexpress.js';

View File

@ -1,199 +1,292 @@
/**
* Module dependencies.
* @private
*/
import HTTP_METHODS from './methods'; import HTTP_METHODS from './methods';
import Settings from './settings'; import Settings from './settings';
import Router, {Route} from './router'; import Router, {Route} from './router';
import Middleware from './middleware'; import Middleware from './middleware';
/**
* Application class.
*/
export default class Application { export default class Application {
/**
* Initialize the application.
*
* - setup default configuration
*
* @private
*/
constructor() { constructor() {
this.routers = []; this.routers = [];
this.DOMLoading = false;
this.settings = new Settings(); this.settings = new Settings();
this.plugins = [];
} }
////////////////////////////////////////////////////////
// Settings /**
set(name, value) { * Assign `setting` to `val`, or return `setting`'s value.
this.settings.set(name, value); *
* app.set('foo', 'bar');
* app.set('foo');
* // => "bar"
*
* @param {String} setting
* @param {*} [val]
* @return {app} for chaining
* @public
*/
set(...args) {
// get behaviour
if (args.length === 1) {
return this.settings.get([args]);
}
// set behaviour
this.settings.set(...args);
return this;
} }
////////////////////////////////////////////////////////
// Start listening requests /**
* Listen for DOM initialization and history state changes.
*
* The callback function is called once the DOM has
* the `document.readyState` equals to 'interactive'.
*
* app.listen(()=> {
* console.log('App is listening requests');
* console.log('DOM is ready!');
* });
*
*
* @param {Function} callback
* @public
*/
listen(callback) { listen(callback) {
document.onreadystatechange = () => { const request = {method: 'GET', uri: window.location.pathname + window.location.search};
const uri = window.location.pathname + window.location.search; const response = {status: 200, statusText: 'OK'};
const method = 'GET'; const currentRoutes = this._routes(request);
const request = {method, uri};
const response = {status: 200, statusText: 'OK'};
// gathers all routes impacted by the current browser location this._callMiddlewareMethod('entered', currentRoutes, request);
const currentRoutes = this._routes(uri, method);
// listen dom events // manage history
if (document.readyState === 'loading') { window.onpopstate = (event) => {
this.DOMLoading = true; if (event.state) {
this._callMiddlewareEntered(currentRoutes, request); const {request, response} = event.state;
} else if (document.readyState === 'interactive') { [
if (!this.DOMLoading) { 'exited',
this._callMiddlewareEntered(currentRoutes, request); 'entered',
} 'updated'
this._callMiddlewareUpdated(currentRoutes, request, response); ].forEach(middlewareMethod => this._callMiddlewareMethod(middlewareMethod, this._routes(request), request, response));
if (callback) {
callback(request, response);
}
} }
}; };
window.addEventListener('beforeunload', () => { // manage page loading/refreshing
this._callMiddlewareExited(); 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);
}
};
document.onreadystatechange = () => {
// DOM ready state
if (document.readyState === 'interactive') {
whenPageIsInteractiveFn();
}
};
if (['interactive', 'complete'].indexOf(document.readyState) !== -1) {
whenPageIsInteractiveFn();
}
} }
////////////////////////////////////////////////////////
// Routes /**
* Returns a new `Router` instance for the _uri_.
* See the Router api docs for details.
*
* app.route('/');
* // => new Router instance
*
* @param {String} uri
* @return {Router} for chaining
*
* @public
*/
route(uri) { route(uri) {
const router = new Router(uri); const router = new Router(uri);
this.routers.push(router); this.routers.push(router);
return router; return router;
} }
/**
* 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')});
*
* // 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|plugin} middleware object, middleware function, plugin
* @return {app} for chaining
*
* @public
*/
use(...args) { use(...args) {
if (args.length === 0) { let {baseUri, router, middleware, plugin} = toParameters(args);
throw new TypeError('use method takes at least a middleware or a router'); if (plugin) {
} this.plugins.push(plugin);
let baseUri, middleware, router, which;
if (args.length === 1) {
[which,] = args;
} else { } else {
[baseUri, which,] = args; if (router) {
} router.baseUri = baseUri;
} else if (middleware) {
if (!(which instanceof Middleware) && (typeof which !== 'function') && !(which instanceof Router)) { router = new Router(baseUri);
throw new TypeError('use method takes at least a middleware or a router'); HTTP_METHODS.forEach((method) => {
} router[method.toLowerCase()](middleware);
});
if (which instanceof Router) {
router = which;
router.baseUri = baseUri;
} else {
middleware = which;
router = new Router(baseUri);
for (const method of 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 { } else {
// calls middleware method throw new TypeError('method takes at least a middleware or a router');
}
this.routers.push(router);
}
return this;
}
/**
* Gather routes from all routers filtered by _uri_ and HTTP _method_.
* See Router#routes() documentation for details.
*
* @private
*/
_routes(request) {
return this.routers.reduce((acc, router) => {
acc.push(...router.routes(this, request));
return acc;
}, []);
}
/**
* Call `Middleware` method or middleware function on _currentRoutes_.
*
* @private
*/
_callMiddlewareMethod(meth, currentRoutes, request, response) {
if (meth === 'exited') {
// currentRoutes, request, response params not needed
this.routers.forEach((router) => {
router.visited().forEach((route) => {
if (route.middleware.exited) {
route.middleware.exited(route.visited);
route.visited = null;
}
});
});
return;
}
currentRoutes.some((route) => {
if (meth === 'updated') {
route.visited = request;
}
if (route.middleware[meth]) {
route.middleware[meth](request, response);
if (route.middleware.next && !route.middleware.next()) {
return true;
}
} else if (meth !== 'entered') {
// calls middleware method
let breakMiddlewareLoop = true; let breakMiddlewareLoop = true;
const next = () => { const next = () => {
breakMiddlewareLoop = false; breakMiddlewareLoop = false;
}; };
route.middleware(request, response, next); route.middleware(request, response, next);
if (breakMiddlewareLoop) { if (breakMiddlewareLoop) {
break; return true;
} }
} }
}
return false;
});
} }
_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) { * Make an ajax request. Manage History#pushState if history object set.
// calls middleware failed method *
if (route.middleware.failed) { * @private
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;
}
}
}
}
/////////////////////////////////////////////////////// _fetch(req, resolve, reject) {
// Ajax request let {method, uri, headers, data, history} = req;
_fetch({method, uri, headers, data}, resolve, reject) {
const httpMethodTransformer = this.get(`http ${method} transformer`); const httpMethodTransformer = this.get(`http ${method} transformer`);
if (httpMethodTransformer) { if (httpMethodTransformer) {
uri = httpMethodTransformer.uri ? httpMethodTransformer.uri({uri, headers, data}) : uri; const {uri: _uriFn, headers: _headersFn, data: _dataFn } = httpMethodTransformer;
headers = httpMethodTransformer.headers ? httpMethodTransformer.headers({uri, headers, data}) : headers; req.uri = _uriFn ? _uriFn({uri, headers, data}) : uri;
data = httpMethodTransformer.data ? httpMethodTransformer.data({uri, headers, data}) : data; req.headers = _headersFn ? _headersFn({uri, headers, data}) : headers;
req.data = _dataFn ? _dataFn({uri, headers, data}) : data;
} }
// calls middleware exited method // calls middleware exited method
this._callMiddlewareExited(); this._callMiddlewareMethod('exited');
// gathers all routes impacted by the uri // gathers all routes impacted by the uri
const currentRoutes = this._routes(uri, method); const currentRoutes = this._routes(req);
// calls middleware entered method // calls middleware entered method
this._callMiddlewareEntered(currentRoutes, {method, uri, headers, data}); this._callMiddlewareMethod('entered', currentRoutes, req);
// invokes http request // invokes http request
this.settings.get('http requester').fetch({method, uri, headers, data}, this.settings.get('http requester').fetch(req,
(request, response) => { (request, response) => {
this._callMiddlewareUpdated(currentRoutes, request, response); if (history) {
window.history.pushState({request, response}, history.title, history.uri);
}
this._callMiddlewareMethod('updated', currentRoutes, request, response);
if (resolve) { if (resolve) {
resolve(request, response); resolve(request, response);
} }
}, },
(request, response) => { (request, response) => {
this._callMiddlewareFailed(currentRoutes, request, response); this._callMiddlewareMethod('failed', currentRoutes, request, response);
if (reject) { if (reject) {
reject(request, response); reject(request, response);
} }
@ -202,45 +295,70 @@ export default class Application {
} }
HTTP_METHODS.reduce((reqProto, method) => { HTTP_METHODS.reduce((reqProto, method) => {
// Middleware methods
/**
* Use the given middleware function or object, with optional _uri_ on
* HTTP methods: get, post, put, delete...
* Default _uri_ is "/".
*
* // middleware function will be applied on path "/"
* app.get((req, res, next) => {console.log('Hello')});
*
* // middleware object will be applied on path "/" and
* app.get(new Middleware());
*
* // get a setting value
* app.set('foo', 'bar');
* app.get('foo');
* // => "bar"
*
* @param {String} uri or setting
* @param {Middleware|Function} middleware object or function
* @return {app} for chaining
* @public
*/
const middlewareMethodName = method.toLowerCase(); const middlewareMethodName = method.toLowerCase();
reqProto[middlewareMethodName] = function(...args) { reqProto[middlewareMethodName] = function(...args) {
if (middlewareMethodName === 'get') { let {baseUri, middleware, which} = toParameters(args);
if (args.length === 0) { if (middlewareMethodName === 'get' && typeof which === 'string') {
throw new TypeError(`${middlewareMethodName} method takes at least a string or a middleware`); return this.settings.get(which);
} else if (args.length === 1) {
const [name] = args;
if (typeof name === 'string') {
return this.settings.get(name);
}
}
} else if (args.length === 0) {
throw new TypeError(`${middlewareMethodName} method takes at least a middleware`);
} }
if (!middleware) {
let baseUri, middleware, which; throw new TypeError(`method takes a middleware ${middlewareMethodName === 'get' ? 'or a string' : ''}`);
if (args.length === 1) {
[which,] = args;
} else {
[baseUri, which,] = args;
} }
if (!(which instanceof Middleware) && (typeof which !== 'function')) {
throw new TypeError(`${middlewareMethodName} method takes at least a middleware`);
}
const router = new Router(); const router = new Router();
middleware = which;
router[middlewareMethodName](baseUri, middleware); router[middlewareMethodName](baseUri, middleware);
this.routers.push(router); this.routers.push(router);
return this;
}; };
// HTTP methods /**
* Ajax request (get, post, put, delete...).
*
* // HTTP GET method
* httpGet('/route1');
*
* // HTTP GET method
* httpGet({uri: '/route1', data: {'p1': 'val1'});
* // uri invoked => /route1?p1=val1
*
* // HTTP GET method with browser history management
* httpGet({uri: '/api/users', history: {state: {foo: "bar"}, title: 'users page', uri: '/view/users'});
*
* Samples above can be applied on other HTTP methods.
*
* @param {String|Object} uri or object containing uri, http headers, data, history
* @param {Function} success callback
* @param {Function} failure callback
* @public
*/
const httpMethodName = 'http'+method.charAt(0).toUpperCase() + method.slice(1).toLowerCase(); const httpMethodName = 'http'+method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
reqProto[httpMethodName] = function(request, resolve, reject) { reqProto[httpMethodName] = function(request, resolve, reject) {
let {uri, headers, data} = request; let {uri, headers, data, history} = request;
if (!uri) { if (!uri) {
uri = request; uri = request;
} }
@ -248,9 +366,27 @@ HTTP_METHODS.reduce((reqProto, method) => {
uri, uri,
method, method,
headers, headers,
data data,
history
}, resolve, reject); }, resolve, reject);
}; };
return reqProto; return reqProto;
}, Application.prototype); }, Application.prototype);
export function toParameters(args) {
let baseUri, middleware, router, plugin, 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, plugin, which};
}

View File

@ -1,9 +1,25 @@
/**
* Module dependencies.
*/
import Application from './application'; import Application from './application';
import Router from './router'; import Router from './router';
import Middleware from './middleware'; import Middleware from './middleware';
/**
* Create a frontexpress application.
*
* @return {Function}
* @api public
*/
const frontexpress = () => new Application(); const frontexpress = () => new Application();
/**
* Expose Router, Middleware constructors.
*/
frontexpress.Router = (baseUri) => new Router(baseUri); frontexpress.Router = (baseUri) => new Router(baseUri);
frontexpress.Middleware = (name) => new Middleware(name); frontexpress.Middleware = Middleware;
export default frontexpress; export default frontexpress;

View File

@ -1 +1,8 @@
export default ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH']; /**
* HTTP method list
* @private
*/
export default ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
// not supported yet
// HEAD', 'CONNECT', 'OPTIONS', 'TRACE';

View File

@ -1,19 +1,85 @@
/**
* Middleware object.
* @public
*/
export default class Middleware { export default class Middleware {
/**
* Middleware initialization
*
* @param {String} middleware name
*/
constructor(name='') { constructor(name='') {
this.name = name; this.name = name;
} }
entered(request) { /**
} * Invoked by the app before an ajax request is sent or
* during the DOM loading (document.readyState === 'loading').
* See Application#_callMiddlewareEntered documentation for details.
*
* Override this method to add your custom behaviour
*
* @param {Object} request
* @public
*/
exited(request) { entered(request) { }
}
updated(request, response) {
}
failed(request, response) { /**
} * Invoked by the app before a new ajax request is sent or before the DOM is unloaded.
* See Application#_callMiddlewareExited documentation for details.
*
* Override this method to add your custom behaviour
*
* @param {Object} request
* @public
*/
exited(request) { }
/**
* Invoked by the app after an ajax request has responded or on DOM ready
* (document.readyState === 'interactive').
* See Application#_callMiddlewareUpdated documentation for details.
*
* Override this method to add your custom behaviour
*
* @param {Object} request
* @param {Object} response
* @public
*/
updated(request, response) { }
/**
* Invoked by the app when an ajax request has failed.
*
* Override this method to add your custom behaviour
*
* @param {Object} request
* @param {Object} response
* @public
*/
failed(request, response) { }
/**
* Allow the hand over to the next middleware object or function.
*
* Override this method and return `false` to break execution of
* middleware chain.
*
* @return {Boolean} `true` by default
*
* @public
*/
next() { next() {
return true; return true;

View File

@ -1,24 +1,48 @@
/**
* Module dependencies.
* @private
*/
export default class Requester { export default class Requester {
fetch({method, uri, headers, data}, resolve, reject) { /**
* Make an ajax request.
*
* @param {Object} request
* @param {Function} success callback
* @param {Function} failure callback
* @private
*/
fetch(request, resolve, reject) {
const {method, uri, headers, data} = request;
const success = (responseText) => { const success = (responseText) => {
resolve( resolve(
{method, uri, headers, data}, request,
{status: 200, statusText: 'OK', responseText} {
status: 200,
statusText: 'OK',
responseText
}
); );
}; };
const fail = ({status, statusText, errorThrown}) => { const fail = ({status, statusText, errorThrown}) => {
const errors = this._analyzeErrors({status, statusText, errorThrown});
reject( reject(
{method, uri, headers, data}, request,
{status, statusText, errorThrown, errors} {
status,
statusText,
errorThrown,
errors: `HTTP ${status} ${statusText?statusText:''}`
}
); );
}; };
const xmlhttp = new XMLHttpRequest(); const xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = () => { xmlhttp.onreadystatechange = () => {
if (xmlhttp.readyState === 4) { if (xmlhttp.readyState === 4) { //XMLHttpRequest.DONE
if (xmlhttp.status === 200) { if (xmlhttp.status === 200) {
success(xmlhttp.responseText); success(xmlhttp.responseText);
} else { } else {
@ -29,9 +53,9 @@ export default class Requester {
try { try {
xmlhttp.open(method, uri, true); xmlhttp.open(method, uri, true);
if (headers) { if (headers) {
for (const header of Object.keys(headers)) { Object.keys(headers).forEach((header) => {
xmlhttp.setRequestHeader(header, headers[header]); xmlhttp.setRequestHeader(header, headers[header]);
} });
} }
if (data) { if (data) {
xmlhttp.send(data); xmlhttp.send(data);
@ -42,38 +66,35 @@ export default class Requester {
fail({errorThrown}); fail({errorThrown});
} }
} }
}
_analyzeErrors(response) { export const httpGetTransformer = {
// manage exceptions uri({uri, headers, data}) {
if (response.errorThrown) { if (!data) {
if (response.errorThrown.name === 'SyntaxError') { return uri;
return 'Problem during data decoding [JSON]';
}
if (response.errorThrown.name === 'TimeoutError') {
return 'Server is taking too long to reply';
}
if (response.errorThrown.name === 'AbortError') {
return 'Request cancelled on server';
}
if (response.errorThrown.name === 'NetworkError') {
return 'A network error occurred';
}
throw response.errorThrown;
} }
let [uriWithoutAnchor, anchor] = [uri, ''];
// manage status const match = /^(.*)(#.*)$/.exec(uri);
if (response.status === 0) { if (match) {
return 'Server access problem. Check your network connection'; [,uriWithoutAnchor, anchor] = /^(.*)(#.*)$/.exec(uri);
} }
if (response.status === 401) { uriWithoutAnchor = Object.keys(data).reduce((gUri, d, index) => {
return 'Your session has expired, Please reconnect. [code: 401]'; gUri += `${(index === 0 && gUri.indexOf('?') === -1)?'?':'&'}${d}=${data[d]}`;
} return gUri;
if (response.status === 404) { }, uriWithoutAnchor);
return 'Page not found on server. [code: 404]'; return uriWithoutAnchor + anchor;
}
if (response.status === 500) {
return 'Internal server error. [code: 500]';
}
return `Unknown error. ${response.statusText?response.statusText:''}`;
} }
} };
// 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

@ -1,7 +1,27 @@
/**
* Module dependencies.
* @private
*/
import HTTP_METHODS from './methods'; import HTTP_METHODS from './methods';
import {toParameters} from './application';
import Middleware from './middleware'; import Middleware from './middleware';
class Route {
/**
* Route object.
* @private
*/
export class Route {
/**
* Initialize the route.
*
* @private
*/
constructor(router, uriPart, method, middleware) { constructor(router, uriPart, method, middleware) {
this.router = router; this.router = router;
this.uriPart = uriPart; this.uriPart = uriPart;
@ -10,6 +30,13 @@ class Route {
this.visited = false; this.visited = false;
} }
/**
* Return route's uri.
*
* @private
*/
get uri() { get uri() {
if (!this.uriPart && !this.method) { if (!this.uriPart && !this.method) {
return undefined; return undefined;
@ -23,26 +50,46 @@ class Route {
return this.router.baseUri; return this.router.baseUri;
} }
if (this.router.baseUri && this.uriPart) {
return (this.router.baseUri.trim() + this.uriPart.trim()).replace(/\/{2,}/, '/');
}
if (this.router.baseUri) { if (this.router.baseUri) {
return this.router.baseUri.trim(); const baseUri = this.router.baseUri.trim();
if (this.uriPart) {
return ( baseUri + this.uriPart.trim()).replace(/\/{2,}/, '/');
}
return baseUri;
} }
return this.uriPart; return this.uriPart;
} }
} }
/**
* Router object.
* @public
*/
const error_middleware_message = 'method takes at least a middleware';
export default class Router { export default class Router {
/**
* Initialize the router.
*
* @private
*/
constructor(uri) { constructor(uri) {
if (uri) { this._baseUri = uri;
this._baseUri = uri;
}
this._routes = []; this._routes = [];
} }
/**
* Do some checks and set _baseUri.
*
* @private
*/
set baseUri(uri) { set baseUri(uri) {
if (!uri) { if (!uri) {
return; return;
@ -53,68 +100,79 @@ export default class Router {
return; return;
} }
if (this._baseUri instanceof RegExp) { if (typeof this._baseUri !== typeof uri) {
throw new TypeError(`the router already contains a regexp uri ${this._baseUri.toString()} It cannot be mixed with ${uri.toString()}`); throw new TypeError('router cannot mix regexp and uri');
}
if (uri instanceof RegExp) {
throw new TypeError(`the router already contains an uri ${this._baseUri.toString()} It cannot be mixed with regexp ${uri.toString()}`);
} }
} }
/**
* Return router's _baseUri.
*
* @private
*/
get baseUri() { get baseUri() {
return this._baseUri; return this._baseUri;
} }
/**
* Add a route to the router.
*
* @private
*/
_add(route) { _add(route) {
this._routes.push(route); this._routes.push(route);
return this; return this;
} }
routes(uri, method) {
/**
* Gather routes from routers filtered by _uri_ and HTTP _method_.
*
* @private
*/
routes(application, request) {
request.params = request.params || {};
const isRouteMatch = application.get('route matcher');
return this._routes.filter((route) => { return this._routes.filter((route) => {
if (!route.uri && !route.method) { return isRouteMatch(request, route);
return true;
}
if (route.method !== method) {
return false;
}
if (!route.uri) {
return true;
}
let uriToCheck = uri;
//remove query string from uri to test
const questionMarkIndex = uriToCheck.indexOf('?');
if (questionMarkIndex >= 0) {
uriToCheck = uriToCheck.slice(0, questionMarkIndex);
}
//remove anchor from uri to test
const hashIndex = uriToCheck.indexOf('#');
if (hashIndex >= 0) {
uriToCheck = uriToCheck.slice(0, hashIndex);
}
if (route.uri instanceof RegExp) {
return uriToCheck.match(route.uri);
}
return route.uri === uriToCheck;
}); });
} }
/**
* Gather visited routes from routers.
*
* @private
*/
visited() { visited() {
return this._routes.filter((route) => { return this._routes.filter(route => route.visited);
return route.visited;
});
} }
/**
* Use the given middleware function or object on this router.
*
* // middleware function
* router.use((req, res, next) => {console.log('Hello')});
*
* // middleware object
* router.use(new Middleware());
*
* @param {Middleware|Function} middleware object or function
* @return {Router} for chaining
*
* @public
*/
use(middleware) { use(middleware) {
if (!(middleware instanceof Middleware) && (typeof middleware !== 'function') ) { if (!(middleware instanceof Middleware) && (typeof middleware !== 'function') ) {
throw new TypeError('use method takes at least a middleware'); throw new TypeError(error_middleware_message);
} }
this._add(new Route(this, undefined, undefined, middleware)); this._add(new Route(this, undefined, undefined, middleware));
@ -122,53 +180,143 @@ export default class Router {
return this; return this;
} }
/**
* Use the given middleware function or object on this router for
* all HTTP methods.
*
* // middleware function
* router.all((req, res, next) => {console.log('Hello')});
*
* // middleware object
* router.all(new Middleware());
*
* @param {Middleware|Function} middleware object or function
* @return {Router} for chaining
*
* @public
*/
all(...args) { all(...args) {
if (args.length === 0) { const {middleware} = toParameters(args);
throw new TypeError('use all method takes at least a middleware'); if (!middleware) {
} throw new TypeError(error_middleware_message);
let middleware;
if (args.length === 1) {
[middleware,] = args;
} else {
[, middleware,] = args;
} }
if (!(middleware instanceof Middleware) && (typeof middleware !== 'function') ) { HTTP_METHODS.forEach((method) => {
throw new TypeError('use all method takes at least a middleware');
}
for (const method of HTTP_METHODS) {
this[method.toLowerCase()](...args); this[method.toLowerCase()](...args);
} });
return this; return this;
} }
} }
for (const method of HTTP_METHODS) { HTTP_METHODS.forEach((method) => {
/**
* Use the given middleware function or object, with optional _uri_ on
* HTTP methods: get, post, put, delete...
* Default _uri_ is "/".
*
* // middleware function will be applied on path "/"
* router.get((req, res, next) => {console.log('Hello')});
*
* // middleware object will be applied on path "/" and
* router.get(new Middleware());
*
* // middleware function will be applied on path "/user"
* router.post('/user', (req, res, next) => {console.log('Hello')});
*
* // middleware object will be applied on path "/user" and
* router.post('/user', new Middleware());
*
* @param {String} uri
* @param {Middleware|Function} middleware object or function
* @return {Router} for chaining
* @public
*/
const methodName = method.toLowerCase(); const methodName = method.toLowerCase();
Router.prototype[methodName] = function(...args) { Router.prototype[methodName] = function(...args) {
if (args.length === 0) { const {baseUri, middleware} = toParameters(args);
throw new TypeError(`use ${methodName} method takes at least a middleware`); if (!middleware) {
} throw new TypeError(error_middleware_message);
let uri, middleware;
if (args.length === 1) {
[middleware,] = args;
} else {
[uri, middleware,] = args;
} }
if (!(middleware instanceof Middleware) && (typeof middleware !== 'function') ) { if (baseUri && this._baseUri && this._baseUri instanceof RegExp) {
throw new TypeError(`use ${methodName} method takes at least a middleware`); throw new TypeError('router cannot mix uri/regexp');
} }
if (uri && this._baseUri && this._baseUri instanceof RegExp) { this._add(new Route(this, baseUri, method, middleware));
throw new TypeError('router contains a regexp cannot mix with route uri/regexp');
}
this._add(new Route(this, uri, method, middleware));
return this; 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

@ -1,52 +1,66 @@
import Requester from './requester'; /**
* Module dependencies.
* @private
*/
import {routeMatcher} from './router';
import Requester, {httpGetTransformer} from './requester';
function errorIfNotFunction(toTest, message) {
if(typeof toTest !== 'function') {
throw new TypeError(message);
}
}
/**
* Settings object.
* @private
*/
export default class Settings {
/**
* Initialize the settings.
*
* - setup default configuration
*
* @private
*/
export default class {
constructor() { constructor() {
// default settings // default settings
this.settings = { this.settings = {
'http requester': new Requester(), 'http requester': new Requester(),
'http GET transformer': httpGetTransformer,
'http GET transformer': { // 'http POST transformer': httpPostTransformer,
uri({uri, headers, data}) { 'route matcher': routeMatcher
if (!data) {
return uri;
}
let anchor = '';
let uriWithoutAnchor = uri;
const hashIndex = uri.indexOf('#');
if (hashIndex >=1) {
uriWithoutAnchor = uri.slice(0, hashIndex);
anchor = uri.slice(hashIndex, uri.length);
}
uriWithoutAnchor = Object.keys(data).reduce((gUri, d, index) => {
if (index === 0 && gUri.indexOf('?') === -1) {
gUri += '?';
} else {
gUri += '&';
}
gUri += `${d}=${data[d]}`;
return gUri;
}, uriWithoutAnchor);
return uriWithoutAnchor + anchor;
},
data({uri, headers, data}) {
return undefined;
}
}
}; };
this.rules = { this.rules = {
'http requester': (requester) => { 'http requester': (requester) => {
if(typeof requester.fetch !== 'function') { errorIfNotFunction(requester.fetch , 'setting http requester has no fetch function');
throw new TypeError('setting http requester has no fetch method'); },
'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');
} }
}; };
} }
/**
* Assign `setting` to `val`
*
* @param {String} setting
* @param {*} [val]
* @private
*/
set(name, value) { set(name, value) {
const checkRules = this.rules[name]; const checkRules = this.rules[name];
if (checkRules) { if (checkRules) {
@ -55,7 +69,15 @@ export default class {
this.settings[name] = value; this.settings[name] = value;
} }
/**
* Return `setting`'s value.
*
* @param {String} setting
* @private
*/
get(name) { get(name) {
return this.settings[name]; return this.settings[name];
} }
}; };

View File

@ -1,18 +1,21 @@
{ {
"name": "frontexpress", "name": "frontexpress",
"version": "0.1.2", "version": "1.2.0",
"description": "Minimalist front end router framework a la express", "description": "Frontexpress manages routes in browser like ExpressJS on Node",
"main": "dist/frontexpress.js", "main": "dist/frontexpress.js",
"jsnext:main": "lib/frontexpress.js",
"scripts": { "scripts": {
"lint": "eslint .", "lint": "eslint .",
"only-test": "mocha --compilers js:babel-core/register", "test-only": "mocha --compilers js:babel-core/register",
"test": "npm run lint && babel-node node_modules/.bin/babel-istanbul cover node_modules/.bin/_mocha", "test": "npm run lint && babel-node node_modules/.bin/babel-istanbul cover node_modules/.bin/_mocha",
"prepublish": "rimraf dist && babel lib -d dist" "gzipsize": "babel-node gzipsize.js",
"frontpackage": "rollup -c rollup.config.dev.js && rollup -c rollup.config.prod.js && npm run gzipsize",
"prepublish": "rimraf dist && babel lib -d dist && npm run frontpackage"
}, },
"author": "Camel Aissani <camel.aissani@gmail.com> (https://nuageprive.fr)", "author": "Camel Aissani <camel.aissani@gmail.com> (https://nuageprive.fr)",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=4.2.6"
}, },
"repository": "camelaissani/frontexpress", "repository": "camelaissani/frontexpress",
"keywords": [ "keywords": [
@ -21,25 +24,38 @@
"web", "web",
"router", "router",
"middleware", "middleware",
"rest",
"restful",
"app", "app",
"api", "api",
"express", "express",
"frontexpress" "frontexpress"
], ],
"devDependencies": { "devDependencies": {
"babel-cli": "^6.10.1", "babel-cli": "^6.18.0",
"babel-core": "^6.10.4", "babel-core": "^6.21.0",
"babel-eslint": "^6.1.0", "babel-eslint": "^7.1.1",
"babel-istanbul": "^0.8.0", "babel-istanbul": "^0.12.1",
"babel-preset-es2015": "^6.9.0", "babel-preset-es2015": "^6.18.0",
"chai": "^3.5.0", "babel-preset-es2015-rollup": "^3.0.0",
"eslint": "^2.11.0", "babel-register": "^6.18.0",
"eslint-loader": "^1.3.0", "bytesize": "^0.2.0",
"istanbul": "^0.4.3", "chai": "^4.*",
"mocha": "^2.5.3", "eslint": "^3.*",
"rimraf": "^2.5.2", "istanbul": "^0.4.5",
"sinon": "^1.17.4" "mocha": "^3.2.0",
} "rimraf": "^2.5.4",
"rollup": "^0.*",
"rollup-plugin-babel": "^2.7.1",
"rollup-plugin-uglify-es": "0.0.1",
"sinon": "^1.*"
},
"files": [
"dist/",
"docs/",
"lib/",
"README.md",
"LICENCE",
"frontexpress.js",
"frontexpress.min.js",
"frontexpress.min.js.map"
]
} }

12
rollup.config.dev.js Normal file
View File

@ -0,0 +1,12 @@
import babel from 'rollup-plugin-babel';
export default {
entry: 'lib/frontexpress.js',
format: 'iife',
moduleName:'frontexpress',
plugins: [babel({
babelrc: false,
presets: ['es2015-rollup']
})],
dest: 'frontexpress.js'
};

17
rollup.config.prod.js Normal file
View File

@ -0,0 +1,17 @@
import uglify from 'rollup-plugin-uglify-es';
import babel from 'rollup-plugin-babel';
export default {
entry: 'lib/frontexpress.js',
format: 'iife',
sourceMap: true,
moduleName:'frontexpress',
dest: 'frontexpress.min.js',
plugins: [
babel({
babelrc: false,
presets: ['es2015-rollup']
}),
uglify()
]
};

View File

@ -1,10 +1,18 @@
/*eslint-env mocha*/ /*eslint-env mocha*/
import chai, {assert} from 'chai'; import chai, {assert} from 'chai';
import sinon from 'sinon'; import sinon from 'sinon';
//import jsdom from 'jsdom';
import frontexpress from '../lib/frontexpress'; import frontexpress from '../lib/frontexpress';
import Requester from '../lib/requester'; import Requester from '../lib/requester';
describe('Application', () => { describe('Application', () => {
class MyMiddleware extends frontexpress.Middleware {
entered() {}
exited() {}
failed() {}
updated() {}
}
let requester; let requester;
describe('generated methods', () => { describe('generated methods', () => {
@ -25,18 +33,108 @@ describe('Application', () => {
}); });
}); });
describe('listen method', () => { // // JSDOM cannot manage pushState/onpopstate/window.location
let eventFn = {}; // see https://github.com/tmpvar/jsdom/issues/1565
//
// describe('listen method with JSDOM', () => {
// before(() => {
// // Init DOM with a fake document
// // <base> and uri (initial uri) allow to do pushState in jsdom
// jsdom.env({
// html:`
// <html>
// <head>
// <base href="http://localhost:8080/"></base>
// </head>
// </html>
// `,
// url: 'http://localhost:8080/',
// done(err, window) {
// global.window = window;
// global.document = window.document;
// window.console = global.console;
// }
// });
// });
// let requester;
// beforeEach(()=>{
// requester = new Requester();
// sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => {
// resolve(
// {uri, method, headers, data},
// {status: 200, statusText: 'OK', responseText:''}
// );
// });
// });
// it('history management without state object', (done) => {
// const spy_pushState = sinon.spy(window.history, 'pushState');
// const app = frontexpress();
// const m = new MyMiddleware();
// const spy_middleware = sinon.stub(m, 'updated');
// app.set('http requester', requester);
// app.use('/api/route1', m);
// app.listen();
// const spy_onpopstate = sinon.spy(window, 'onpopstate');
// app.httpGet({uri:'/api/route1', history: {
// uri: '/route1',
// title: 'route1'
// }},
// (req, res) => {
// // On request succeeded
// assert(spy_onpopstate.callCount === 0);
// assert(spy_pushState.calledOnce);
// assert(spy_middleware.calledOnce);
// spy_middleware.reset();
// window.history.back();
// window.history.forward();
// assert(spy_onpopstate.calledOnce);
// assert(spy_middleware.calledOnce);
// done();
// });
// });
// });
describe('listen method', () => {
// Here I cannot use jsdon to make these tests :(
// JSDOM cannot simulate readyState changes
beforeEach(() => { beforeEach(() => {
const browserHistory = [{uri: '/'}];
let browserHistoryIndex = 0;
global.document = {}; global.document = {};
global.window = { global.window = {
addEventListener(eventType, callback) {
eventFn[eventType] = callback;
},
location: { location: {
pathname: '/route1', pathname: '/route1',
search: '?a=b' search: '?a=b'
},
history: {
pushState(state, title, pathname) {
browserHistory.push({uri: pathname, state});
browserHistoryIndex++;
global.window.location.pathname = browserHistory[browserHistoryIndex].uri;
},
forward() {
browserHistoryIndex++;
global.window.location.pathname = browserHistory[browserHistoryIndex].uri;
if (browserHistory[browserHistoryIndex].state) {
window.onpopstate({state:browserHistory[browserHistoryIndex].state});
}
},
back() {
browserHistoryIndex--;
global.window.location.pathname = browserHistory[browserHistoryIndex].uri;
if (browserHistory[browserHistoryIndex].state) {
window.onpopstate({state: browserHistory[browserHistoryIndex].state});
}
}
} }
}; };
}); });
@ -55,7 +153,7 @@ describe('Application', () => {
it('with middleware object readyState===loading', (done) => { it('with middleware object readyState===loading', (done) => {
const app = frontexpress(); const app = frontexpress();
const m = frontexpress.Middleware(); const m = new MyMiddleware();
sinon.stub(m, 'entered', () => { sinon.stub(m, 'entered', () => {
done(); done();
}); });
@ -70,7 +168,7 @@ describe('Application', () => {
it('with middleware object readyState===interactive', (done) => { it('with middleware object readyState===interactive', (done) => {
const app = frontexpress(); const app = frontexpress();
const m = frontexpress.Middleware(); const m = new MyMiddleware();
sinon.stub(m, 'updated', () => { sinon.stub(m, 'updated', () => {
done(); done();
}); });
@ -85,7 +183,7 @@ describe('Application', () => {
it('with middleware object event beforeunload', (done) => { it('with middleware object event beforeunload', (done) => {
const app = frontexpress(); const app = frontexpress();
const m = frontexpress.Middleware(); const m = new MyMiddleware();
sinon.stub(m, 'exited', () => { sinon.stub(m, 'exited', () => {
done(); done();
}); });
@ -93,13 +191,111 @@ describe('Application', () => {
app.use('/route1', m); app.use('/route1', m);
app.listen(() => { app.listen(() => {
//simulate beforeunload //simulate beforeunload
eventFn['beforeunload'](); window.onbeforeunload();
}); });
//simulate readystatechange //simulate readystatechange
document.readyState = 'interactive'; document.readyState = 'interactive';
document.onreadystatechange(); document.onreadystatechange();
}); });
it('history management without state object', (done) => {
let historyObj;
const requester = new Requester();
sinon.stub(requester, 'fetch', ({uri, method, headers, data, history}, resolve, reject) => {
resolve(
{uri, method, headers, data, history},
{status: 200, statusText: 'OK', responseText:''}
);
});
const spy_pushState = sinon.spy(window.history, 'pushState');
const app = frontexpress();
const m = new MyMiddleware();
const spy_middleware = sinon.stub(m, 'updated', (req, res) => {
historyObj = req.history;
});
app.set('http requester', requester);
app.use('/api/route1', m);
app.listen();
const spy_onpopstate = sinon.spy(window, 'onpopstate');
app.httpGet({uri:'/api/route1', history: {
uri: '/route1',
title: 'route1'
}},
(req, res) => {
// On request succeeded
assert(spy_onpopstate.callCount === 0);
assert(spy_pushState.calledOnce);
assert(spy_middleware.calledOnce);
spy_middleware.reset();
window.history.back();
window.history.forward();
assert(spy_onpopstate.calledOnce);
assert(spy_middleware.calledOnce);
assert(historyObj);
assert(historyObj.uri === '/route1');
assert(historyObj.title === 'route1');
done();
});
});
it('history management with state object', (done) => {
let historyObj;
const requester = new Requester();
sinon.stub(requester, 'fetch', ({uri, method, headers, data, history}, resolve, reject) => {
resolve(
{uri, method, headers, data, history},
{status: 200, statusText: 'OK', responseText:''}
);
});
const spy_pushState = sinon.spy(window.history, 'pushState');
const app = frontexpress();
const m = new MyMiddleware();
const spy_middleware = sinon.stub(m, 'updated', (req, res) => {
historyObj = req.history;
});
app.set('http requester', requester);
app.use('/api/route1', m);
app.listen();
const spy_onpopstate = sinon.spy(window, 'onpopstate');
app.httpGet({uri:'/api/route1', history: {
uri: '/route1',
title: 'route1',
state: {a: 'b', c: 'd'}
}},
(req, res) => {
// On request succeeded
assert(spy_onpopstate.callCount === 0);
assert(spy_pushState.calledOnce);
assert(spy_middleware.calledOnce);
spy_middleware.reset();
window.history.back();
window.history.forward();
assert(spy_onpopstate.calledOnce);
assert(spy_middleware.calledOnce);
assert(historyObj);
assert(historyObj.uri === '/route1');
assert(historyObj.title === 'route1');
assert(historyObj.state);
assert(historyObj.state.a === 'b');
assert(historyObj.state.c === 'd');
done();
});
});
}); });
describe('set/get setting method', () => { describe('set/get setting method', () => {
@ -114,6 +310,7 @@ describe('Application', () => {
const app = frontexpress(); const app = frontexpress();
app.set('http requester', requester); app.set('http requester', requester);
assert(app.get('http requester') === requester); assert(app.get('http requester') === requester);
assert(app.set('http requester') === requester);
}); });
it('bad core setting', () => { it('bad core setting', () => {
@ -214,7 +411,7 @@ describe('Application', () => {
}); });
it('middleware as object on path /', (done) => { it('middleware as object on path /', (done) => {
const middleware = frontexpress.Middleware('on path /'); const middleware = new MyMiddleware('on path /');
const spy = sinon.spy(middleware, 'updated'); const spy = sinon.spy(middleware, 'updated');
const app = frontexpress(); const app = frontexpress();
@ -232,7 +429,7 @@ describe('Application', () => {
}); });
it('middleware as object on path /route1', (done) => { it('middleware as object on path /route1', (done) => {
const middleware = frontexpress.Middleware('on path /route1'); const middleware = new MyMiddleware('on path /route1');
const spy = sinon.spy(middleware, 'updated'); const spy = sinon.spy(middleware, 'updated');
const app = frontexpress(); const app = frontexpress();
@ -258,7 +455,7 @@ describe('Application', () => {
); );
}); });
const middleware = frontexpress.Middleware('on path /'); const middleware = new MyMiddleware('on path /');
const spy = sinon.spy(middleware, 'failed'); const spy = sinon.spy(middleware, 'failed');
const app = frontexpress(); const app = frontexpress();
@ -315,7 +512,7 @@ describe('Application', () => {
}); });
it('router with base uri', (done)=> { it('router with base uri', (done)=> {
const middleware = frontexpress.Middleware('get middleware'); const middleware = new MyMiddleware('get middleware');
const spy = sinon.spy(middleware, 'updated'); const spy = sinon.spy(middleware, 'updated');
const app = frontexpress(); const app = frontexpress();
@ -338,12 +535,12 @@ describe('Application', () => {
it('use two routers', (done)=> { it('use two routers', (done)=> {
const router1 = frontexpress.Router(); const router1 = frontexpress.Router();
const m1 = frontexpress.Middleware(); const m1 = new MyMiddleware();
const spy1 = sinon.spy(m1, 'updated'); const spy1 = sinon.spy(m1, 'updated');
router1.get('/subroute1', m1); router1.get('/subroute1', m1);
const router2 = frontexpress.Router(); const router2 = frontexpress.Router();
const m2 = frontexpress.Middleware(); const m2 = new MyMiddleware();
const spy2 = sinon.spy(m2, 'updated'); const spy2 = sinon.spy(m2, 'updated');
router2.get('/subroute2', m2); router2.get('/subroute2', m2);
@ -431,7 +628,7 @@ describe('Application', () => {
}); });
it('middleware as object on path /', (done) => { it('middleware as object on path /', (done) => {
const middleware = frontexpress.Middleware('on path /'); const middleware = new MyMiddleware('on path /');
const spy = sinon.spy(middleware, 'updated'); const spy = sinon.spy(middleware, 'updated');
const app = frontexpress(); const app = frontexpress();
@ -449,7 +646,7 @@ describe('Application', () => {
}); });
it('middleware as object on path /route1', (done) => { it('middleware as object on path /route1', (done) => {
const middleware = frontexpress.Middleware('on path /route1'); const middleware = new MyMiddleware('on path /route1');
const spy = sinon.spy(middleware, 'updated'); const spy = sinon.spy(middleware, 'updated');
const app = frontexpress(); const app = frontexpress();
@ -611,7 +808,7 @@ describe('Application', () => {
const app = frontexpress(); const app = frontexpress();
app.set('http requester', requester); app.set('http requester', requester);
const getMiddleware = frontexpress.Middleware('get middleware'); const getMiddleware = new MyMiddleware('get middleware');
const spy_get_entered = sinon.spy(getMiddleware, 'entered'); const spy_get_entered = sinon.spy(getMiddleware, 'entered');
const spy_get_updated = sinon.spy(getMiddleware, 'updated'); const spy_get_updated = sinon.spy(getMiddleware, 'updated');
const spy_get_exited = sinon.spy(getMiddleware, 'exited'); const spy_get_exited = sinon.spy(getMiddleware, 'exited');
@ -639,17 +836,17 @@ describe('Application', () => {
const app = frontexpress(); const app = frontexpress();
app.set('http requester', requester); app.set('http requester', requester);
const allMiddleware = frontexpress.Middleware('all middleware'); const allMiddleware = new MyMiddleware('all middleware');
const spy_all_entered = sinon.spy(allMiddleware, 'entered'); const spy_all_entered = sinon.spy(allMiddleware, 'entered');
const spy_all_updated = sinon.spy(allMiddleware, 'updated'); const spy_all_updated = sinon.spy(allMiddleware, 'updated');
const spy_all_exited = sinon.spy(allMiddleware, 'exited'); const spy_all_exited = sinon.spy(allMiddleware, 'exited');
const getMiddleware = frontexpress.Middleware('get middleware'); const getMiddleware = new MyMiddleware('get middleware');
const spy_get_entered = sinon.spy(getMiddleware, 'entered'); const spy_get_entered = sinon.spy(getMiddleware, 'entered');
const spy_get_updated = sinon.spy(getMiddleware, 'updated'); const spy_get_updated = sinon.spy(getMiddleware, 'updated');
const spy_get_exited = sinon.spy(getMiddleware, 'exited'); const spy_get_exited = sinon.spy(getMiddleware, 'exited');
const postMiddleware = frontexpress.Middleware('post middleware'); const postMiddleware = new MyMiddleware('post middleware');
const spy_post_entered = sinon.spy(postMiddleware, 'entered'); const spy_post_entered = sinon.spy(postMiddleware, 'entered');
const spy_post_updated = sinon.spy(postMiddleware, 'updated'); const spy_post_updated = sinon.spy(postMiddleware, 'updated');
const spy_post_exited = sinon.spy(postMiddleware, 'exited'); const spy_post_exited = sinon.spy(postMiddleware, 'exited');
@ -722,7 +919,7 @@ describe('Application', () => {
const app = frontexpress(); const app = frontexpress();
app.set('http requester', requester); app.set('http requester', requester);
const getMiddleware = frontexpress.Middleware('get middleware'); const getMiddleware = new MyMiddleware('get middleware');
const spy_get_failed = sinon.spy(getMiddleware, 'failed'); const spy_get_failed = sinon.spy(getMiddleware, 'failed');
@ -741,10 +938,10 @@ describe('Application', () => {
const app = frontexpress(); const app = frontexpress();
app.set('http requester', requester); app.set('http requester', requester);
const m1 = frontexpress.Middleware('m1'); const m1 = new MyMiddleware('m1');
const m2 = frontexpress.Middleware('m2'); const m2 = new MyMiddleware('m2');
m2.next = () => false; m2.next = () => false;
const m3 = frontexpress.Middleware('m3'); const m3 = new MyMiddleware('m3');
const spy_m1 = sinon.spy(m1, 'updated'); const spy_m1 = sinon.spy(m1, 'updated');
const spy_m2 = sinon.spy(m2, 'updated'); const spy_m2 = sinon.spy(m2, 'updated');
@ -792,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();
});
});
}); });

View File

@ -18,10 +18,10 @@ describe('frontexpress', () => {
it('test Middleware class exposed', () => { it('test Middleware class exposed', () => {
assert(frontexpress.Middleware); assert(frontexpress.Middleware);
assert(frontexpress.Middleware() instanceof Middleware); assert(new frontexpress.Middleware() instanceof Middleware);
const m1 = frontexpress.Middleware(); const m1 = new frontexpress.Middleware();
const m2 = frontexpress.Middleware(); const m2 = new frontexpress.Middleware();
assert(m1 !== m2); assert(m1 !== m2);
}); });
@ -33,4 +33,4 @@ describe('frontexpress', () => {
const app2 = frontexpress(); const app2 = frontexpress();
assert(app1 !== app2); assert(app1 !== app2);
}); });
}); });

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

@ -111,7 +111,7 @@ describe('Test sample from README', () => {
const router = frontexpress.Router(); const router = frontexpress.Router();
// middleware that is specific to this router // middleware which is specific to this router
router.use((req, res, next) => { router.use((req, res, next) => {
spy_log(`Time: ${Date.now()}`); spy_log(`Time: ${Date.now()}`);
next(); next();
@ -148,4 +148,4 @@ describe('Test sample from README', () => {
}); });
}); });
}); });

View File

@ -378,15 +378,16 @@ describe('Requester', () => {
}); });
}); });
it('request returns unknown error', () => { // Removed for reducing size of frontexpress
const requester = new Requester(); // it('request returns unknown error', () => {
// const requester = new Requester();
const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'BlaBlaError'); // const {stub_open, stub_send} = xHttpWillThrow(xhttp, 'BlaBlaError');
chai.expect(() => { // chai.expect(() => {
requester.fetch({method: 'GET', uri:'/route1'}); // requester.fetch({method: 'GET', uri:'/route1'});
}).to.throw(/BlaBlaError/); // }).to.throw(/BlaBlaError/);
}); // });
}); });
}); });

View File

@ -4,6 +4,9 @@ import sinon from 'sinon';
import frontexpress from '../lib/frontexpress'; import frontexpress from '../lib/frontexpress';
import HTTP_METHODS from '../lib/methods'; import HTTP_METHODS from '../lib/methods';
const application = frontexpress();
const routeMatcher = application.get('route matcher');
describe('Router', () => { describe('Router', () => {
describe('generated methods', () => { describe('generated methods', () => {
@ -12,6 +15,7 @@ describe('Router', () => {
assert(typeof router.all === 'function'); assert(typeof router.all === 'function');
assert(typeof router.get === 'function'); assert(typeof router.get === 'function');
assert(typeof router.put === 'function'); assert(typeof router.put === 'function');
assert(typeof router.patch === 'function');
assert(typeof router.post === 'function'); assert(typeof router.post === 'function');
assert(typeof router.delete === 'function'); assert(typeof router.delete === 'function');
}); });
@ -46,7 +50,7 @@ describe('Router', () => {
router.get(middleware); router.get(middleware);
const r1 = router.routes('/', 'GET'); const r1 = router.routes(application, {uri: '/', method: 'GET'});
assert(r1.length === 1); assert(r1.length === 1);
assert(r1[0].uri === undefined); assert(r1[0].uri === undefined);
assert(r1[0].method === 'GET'); assert(r1[0].method === 'GET');
@ -64,37 +68,37 @@ describe('Router', () => {
.post('/route2', middleware2) .post('/route2', middleware2)
.all('/route3', middleware3); .all('/route3', middleware3);
const r1 = router.routes('/route1', 'GET'); const r1 = router.routes(application, {uri: '/route1', method: 'GET'});
assert(r1.length === 1); assert(r1.length === 1);
assert(r1[0].uri === '/route1'); assert(r1[0].uri === '/route1');
assert(r1[0].method === 'GET'); assert(r1[0].method === 'GET');
assert(r1[0].middleware === middleware1); 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.length === 1);
assert(r2[0].uri === '/route2'); assert(r2[0].uri === '/route2');
assert(r2[0].method === 'POST'); assert(r2[0].method === 'POST');
assert(r2[0].middleware === middleware2); 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.length === 1);
assert(r3[0].uri === '/route3'); assert(r3[0].uri === '/route3');
assert(r3[0].method === 'GET'); assert(r3[0].method === 'GET');
assert(r3[0].middleware === middleware3); assert(r3[0].middleware === middleware3);
r3 = router.routes('/route3', 'POST'); r3 = router.routes(application, {uri: '/route3', method: 'POST'});
assert(r3.length === 1); assert(r3.length === 1);
assert(r3[0].uri === '/route3'); assert(r3[0].uri === '/route3');
assert(r3[0].method === 'POST'); assert(r3[0].method === 'POST');
assert(r3[0].middleware === middleware3); assert(r3[0].middleware === middleware3);
r3 = router.routes('/route3', 'PUT'); r3 = router.routes(application, {uri: '/route3', method: 'PUT'});
assert(r3.length === 1); assert(r3.length === 1);
assert(r3[0].uri === '/route3'); assert(r3[0].uri === '/route3');
assert(r3[0].method === 'PUT'); assert(r3[0].method === 'PUT');
assert(r3[0].middleware === middleware3); assert(r3[0].middleware === middleware3);
r3 = router.routes('/route3', 'DELETE'); r3 = router.routes(application, {uri: '/route3', method: 'DELETE'});
assert(r3.length === 1); assert(r3.length === 1);
assert(r3[0].uri === '/route3'); assert(r3[0].uri === '/route3');
assert(r3[0].method === 'DELETE'); assert(r3[0].method === 'DELETE');
@ -103,11 +107,11 @@ describe('Router', () => {
it('no root path and regexp uri', ()=> { it('no root path and regexp uri', ()=> {
const router = frontexpress.Router(); const router = frontexpress.Router();
const middleware = frontexpress.Middleware(); const middleware = new frontexpress.Middleware();
router.get(/^\/route1/, middleware); 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.length === 1);
assert(r[0].uri instanceof RegExp); assert(r[0].uri instanceof RegExp);
assert(r[0].uri.toString() === new RegExp('^\/route1').toString()); assert(r[0].uri.toString() === new RegExp('^\/route1').toString());
@ -118,9 +122,9 @@ describe('Router', () => {
it('with root path /route1 and path /subroute', () => { it('with root path /route1 and path /subroute', () => {
const router = frontexpress.Router('/route1'); const router = frontexpress.Router('/route1');
router.get('/subroute', frontexpress.Middleware()); 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.length === 1);
assert(r[0].uri === '/route1/subroute'); assert(r[0].uri === '/route1/subroute');
}); });
@ -128,9 +132,9 @@ describe('Router', () => {
it('with root path /route1 and no path uri', () => { it('with root path /route1 and no path uri', () => {
const router = frontexpress.Router('/route1'); const router = frontexpress.Router('/route1');
router.get(frontexpress.Middleware()); 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.length === 1);
assert(r[0].uri === '/route1'); assert(r[0].uri === '/route1');
}); });
@ -138,9 +142,9 @@ describe('Router', () => {
it('duplicate / in route', () => { it('duplicate / in route', () => {
const router = frontexpress.Router('/route1/'); const router = frontexpress.Router('/route1/');
router.get('/subroute', frontexpress.Middleware()); 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.length === 1);
assert(r[0].uri === '/route1/subroute'); assert(r[0].uri === '/route1/subroute');
}); });
@ -148,9 +152,9 @@ describe('Router', () => {
it('spaces in route', () => { it('spaces in route', () => {
let router = frontexpress.Router(' /route1 '); let router = frontexpress.Router(' /route1 ');
router.get('/subroute ', frontexpress.Middleware()); 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.length === 1);
assert(r[0].uri === '/route1/subroute'); assert(r[0].uri === '/route1/subroute');
@ -158,9 +162,9 @@ describe('Router', () => {
router = frontexpress.Router(' /route1 '); router = frontexpress.Router(' /route1 ');
router.get(frontexpress.Middleware()); router.get(new frontexpress.Middleware());
r = router.routes('/route1', 'GET'); r = router.routes(application, {uri: '/route1', method: 'GET'});
assert(r.length === 1); assert(r.length === 1);
assert(r[0].uri === '/route1'); assert(r[0].uri === '/route1');
}); });
@ -168,9 +172,9 @@ describe('Router', () => {
it('route with query string', () => { it('route with query string', () => {
let router = frontexpress.Router('/route1 '); let router = frontexpress.Router('/route1 ');
router.get('/subroute', frontexpress.Middleware()); 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.length === 1);
assert(r[0].uri === '/route1/subroute'); assert(r[0].uri === '/route1/subroute');
assert(r[0].data === undefined); assert(r[0].data === undefined);
@ -179,9 +183,9 @@ describe('Router', () => {
it('route with anchor', () => { it('route with anchor', () => {
let router = frontexpress.Router('/route1 '); let router = frontexpress.Router('/route1 ');
router.get('/subroute', frontexpress.Middleware()); 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.length === 1);
assert(r[0].uri === '/route1/subroute'); assert(r[0].uri === '/route1/subroute');
assert(r[0].data === undefined); assert(r[0].data === undefined);
@ -190,9 +194,9 @@ describe('Router', () => {
it('route with query string and anchor', () => { it('route with query string and anchor', () => {
let router = frontexpress.Router('/route1 '); let router = frontexpress.Router('/route1 ');
router.get('/subroute', frontexpress.Middleware()); 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.length === 1);
assert(r[0].uri === '/route1/subroute'); assert(r[0].uri === '/route1/subroute');
assert(r[0].data === undefined); assert(r[0].data === undefined);
@ -214,7 +218,7 @@ describe('Router', () => {
it('only middleware as argument', () => { it('only middleware as argument', () => {
const router = frontexpress.Router('/route1'); const router = frontexpress.Router('/route1');
const middleware = frontexpress.Middleware(); const middleware = new frontexpress.Middleware();
const spied_methods = []; const spied_methods = [];
for (const method of HTTP_METHODS) { for (const method of HTTP_METHODS) {
@ -230,7 +234,7 @@ describe('Router', () => {
it('with path /route1 and middleware as arguments', () => { it('with path /route1 and middleware as arguments', () => {
const router = frontexpress.Router(); const router = frontexpress.Router();
const middleware = frontexpress.Middleware(); const middleware = new frontexpress.Middleware();
const spied_methods = []; const spied_methods = [];
for (const method of HTTP_METHODS) { for (const method of HTTP_METHODS) {
@ -260,11 +264,11 @@ describe('Router', () => {
it('only middleware as argument', () => { it('only middleware as argument', () => {
const router = frontexpress.Router('/'); const router = frontexpress.Router('/');
const middleware = frontexpress.Middleware(); const middleware = new frontexpress.Middleware();
router.get(middleware); router.get(middleware);
const r = router.routes('/', 'GET'); const r = router.routes(application, {uri: '/', method: 'GET'});
assert(r.length === 1); assert(r.length === 1);
assert(r[0].uri === '/'); assert(r[0].uri === '/');
assert(r[0].method === 'GET'); assert(r[0].method === 'GET');
@ -273,11 +277,11 @@ describe('Router', () => {
it('with path /route1 and middleware as arguments', () => { it('with path /route1 and middleware as arguments', () => {
const router = frontexpress.Router(); const router = frontexpress.Router();
const middleware = frontexpress.Middleware(); const middleware = new frontexpress.Middleware();
router.get('/route1', middleware); 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.length === 1);
assert(r[0].uri === '/route1'); assert(r[0].uri === '/route1');
assert(r[0].method === 'GET'); assert(r[0].method === 'GET');
@ -286,16 +290,16 @@ describe('Router', () => {
it('router with regexp and route with /route1', () => { it('router with regexp and route with /route1', () => {
const router = frontexpress.Router(/^\//); const router = frontexpress.Router(/^\//);
const middleware = frontexpress.Middleware(); const middleware = new frontexpress.Middleware();
chai.expect(() => router.get('/route1', middleware)).to.throw(TypeError); chai.expect(() => router.get('/route1', middleware)).to.throw(TypeError);
}); });
it('router with regexp and route without uri', () => { it('router with regexp and route without uri', () => {
const router = frontexpress.Router(/^\/part/); const router = frontexpress.Router(/^\/part/);
const middleware = frontexpress.Middleware(); const middleware = new frontexpress.Middleware();
router.get(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.length === 1);
assert(r[0].uri instanceof RegExp); assert(r[0].uri instanceof RegExp);
assert(r[0].uri.toString() === new RegExp('^\/part').toString()); assert(r[0].uri.toString() === new RegExp('^\/part').toString());
@ -303,4 +307,113 @@ describe('Router', () => {
assert(r[0].middleware === middleware); 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,17 +1,49 @@
/*eslint-env mocha*/ /*eslint-env mocha*/
import {assert} from 'chai'; import chai, {assert} from 'chai';
import Settings from '../lib/settings'; import Settings from '../lib/settings';
describe('Settings', () => { describe('Settings', () => {
const settings = new Settings(); const settings = new Settings();
describe('http GET method transformer', () => { 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', () => { it('simple uri', () => {
const uriFn = settings.get('http GET transformer').uri; const uriFn = settings.get('http GET transformer').uri;
const dataFn = settings.get('http GET transformer').data; const dataFn = settings.get('http GET transformer').data;
assert(uriFn({uri: '/route', data:{a:'b', c:'d'}}) === '/route?a=b&c=d'); assert(uriFn({uri: '/route', data:{a:'b', c:'d'}}) === '/route?a=b&c=d');
assert(dataFn({uri: '/route', data:{a:'b', c:'d'}}) === undefined); assert(dataFn === undefined);
}); });
it('uri with query string', () => { it('uri with query string', () => {
@ -19,7 +51,7 @@ describe('Settings', () => {
const dataFn = settings.get('http GET transformer').data; const dataFn = settings.get('http GET transformer').data;
assert(uriFn({uri: '/route?x=y&z=a', data:{a:'b', c:'d'}}) === '/route?x=y&z=a&a=b&c=d'); 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); assert(dataFn === undefined);
}); });
it('uri with query string and anchor', () => { it('uri with query string and anchor', () => {
@ -27,7 +59,7 @@ describe('Settings', () => {
const dataFn = settings.get('http GET transformer').data; const dataFn = settings.get('http GET transformer').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(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); assert(dataFn === undefined);
}); });
}); });
}); });