Compare commits

...

81 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
Camel Aissani
409c4cd891 Merge branch 'master' of github.com:camelaissani/frontexpress 2016-07-14 23:38:54 +02:00
Camel Aissani
0a5bfc585e update version 2016-07-14 23:38:40 +02:00
Camel Aissani
13c39d9e13 Update README.md 2016-07-14 23:22:23 +02:00
Camel Aissani
6e04aa53cf Update README.md 2016-07-14 23:09:13 +02:00
Camel Aissani
ad83bfb020 removed post transformation - completed unit tests 2016-07-14 23:05:11 +02:00
Camel Aissani
15637d53bd Merge branch 'master' of github.com:camelaissani/frontexpress 2016-07-14 15:14:33 +02:00
Camel Aissani
bf423fb19c better usage of settings 2016-07-14 15:14:16 +02:00
Camel Aissani
7402f4ba22 Update README.md 2016-07-10 15:18:14 +02:00
Camel Aissani
48f8d7e333 changed to a better title font 2016-07-10 15:02:30 +02:00
Camel Aissani
117e5c6974 new release - only fixed documentation 2016-07-10 09:53:41 +02:00
Camel Aissani
32ee6a8264 Update README.md 2016-07-10 01:37:38 +02:00
Camel Aissani
cd94fe18ed Update README.md 2016-07-10 01:34:42 +02:00
33 changed files with 3445 additions and 710 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
dist

View File

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

View File

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

400
README.md
View File

@ -1,32 +1,272 @@
# frontexpress
![frontexpress](http://fontmeme.com/embed.php?text=frontexpress&name=Atype%201%20Light.ttf&size=90&style_color=6F6F75)
Minimalist front end router framework a 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)
[![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)
```js
import frontexpress from 'frontexpress';
const app = frontexpress();
// listen HTTP GET request on path (/)
app.get('/', (req, res) => {
window.alert('Hello World');
});
// start listening frontend application requests
app.listen();
```
![dependencies](https://img.shields.io/gemnasium/mathiasbynens/he.svg)
![Size Shield](https://img.shields.io/badge/size-3.26kb-brightgreen.svg)
## Installation
### From npm repository
```bash
$ npm install frontexpress
```
## Quick Start
### From bower repository
The quickest way to get started with frontexpress is to clone [frontexpress-demo](https://github.com/camelaissani/frontexpress-demo)
```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
@ -44,130 +284,6 @@ $ npm install
$ 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
[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,198 +1,292 @@
/**
* Module dependencies.
* @private
*/
import HTTP_METHODS from './methods';
import Settings from './settings';
import Router, {Route} from './router';
import Middleware from './middleware';
import Requester, {HTTP_METHODS} from './requester';
/**
* Application class.
*/
export default class Application {
/**
* Initialize the application.
*
* - setup default configuration
*
* @private
*/
constructor() {
this.routers = [];
this.requester = new Requester();
this.DOMLoading = false;
this.settingsDef = {
'http-requester': (requester) => {this.requester = requester;}
};
this.settings = new Settings();
this.plugins = [];
}
////////////////////////////////////////////////////////
// Setting
set(name, value) {
const settingFn = this.settingsDef[name];
if (!settingFn) {
throw new ReferenceError(`unsupported setting ${name}`);
/**
* Assign `setting` to `val`, or return `setting`'s 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]);
}
settingFn(value);
// 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) {
document.onreadystatechange = () => {
const uri = window.location.pathname + window.location.search;
const method = 'GET';
const request = {method, uri};
const response = {status: 200, statusText: 'OK'};
const request = {method: 'GET', uri: window.location.pathname + window.location.search};
const response = {status: 200, statusText: 'OK'};
const currentRoutes = this._routes(request);
// gathers all routes impacted by the current browser location
const currentRoutes = this._routes(uri, method);
this._callMiddlewareMethod('entered', currentRoutes, request);
// listen dom events
if (document.readyState === 'loading') {
this.DOMLoading = true;
this._callMiddlewareEntered(currentRoutes, request);
} else if (document.readyState === 'interactive') {
if (!this.DOMLoading) {
this._callMiddlewareEntered(currentRoutes, request);
}
this._callMiddlewareUpdated(currentRoutes, request, response);
if (callback) {
callback(request, response);
}
// manage history
window.onpopstate = (event) => {
if (event.state) {
const {request, response} = event.state;
[
'exited',
'entered',
'updated'
].forEach(middlewareMethod => this._callMiddlewareMethod(middlewareMethod, this._routes(request), request, response));
}
};
window.addEventListener('beforeunload', () => {
this._callMiddlewareExited();
});
// manage page loading/refreshing
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) {
const router = new Router(uri);
this.routers.push(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) {
if (args.length === 0) {
throw new TypeError('use method takes at least a middleware or a router');
}
let baseUri, middleware, router, which;
if (args.length === 1) {
[which,] = args;
let {baseUri, router, middleware, plugin} = toParameters(args);
if (plugin) {
this.plugins.push(plugin);
} else {
[baseUri, which,] = args;
}
if (!(which instanceof Middleware) && (typeof which !== 'function') && !(which instanceof Router)) {
throw new TypeError('use method takes at least a middleware or a router');
}
if (which instanceof Router) {
router = which;
router.baseUri = baseUri;
} else {
middleware = which;
router = new Router(baseUri);
for (const method of Object.keys(HTTP_METHODS)) {
router[method.toLowerCase()](middleware);
}
}
this.routers.push(router);
}
_routes(uri, method) {
const currentRoutes = [];
for (const router of this.routers) {
const routes = router.routes(uri, method);
currentRoutes.push(...routes);
}
return currentRoutes;
}
_callMiddlewareEntered(currentRoutes, request) {
for (const route of currentRoutes) {
if (route.middleware.entered) {
route.middleware.entered(request);
}
if (route.middleware.next && !route.middleware.next()) {
break;
}
}
}
_callMiddlewareUpdated(currentRoutes, request, response) {
for (const route of currentRoutes) {
route.visited = request;
// calls middleware updated method
if (route.middleware.updated) {
route.middleware.updated(request, response);
if (route.middleware.next && !route.middleware.next()) {
break;
}
if (router) {
router.baseUri = baseUri;
} else if (middleware) {
router = new Router(baseUri);
HTTP_METHODS.forEach((method) => {
router[method.toLowerCase()](middleware);
});
} 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;
const next = () => {
breakMiddlewareLoop = false;
};
route.middleware(request, response, next);
if (breakMiddlewareLoop) {
break;
return true;
}
}
}
return false;
});
}
_callMiddlewareExited() {
/**
* Make an ajax request. Manage History#pushState if history object set.
*
* @private
*/
_fetch(req, resolve, reject) {
let {method, uri, headers, data, history} = req;
const httpMethodTransformer = this.get(`http ${method} transformer`);
if (httpMethodTransformer) {
const {uri: _uriFn, headers: _headersFn, data: _dataFn } = httpMethodTransformer;
req.uri = _uriFn ? _uriFn({uri, headers, data}) : uri;
req.headers = _headersFn ? _headersFn({uri, headers, data}) : headers;
req.data = _dataFn ? _dataFn({uri, headers, data}) : data;
}
// calls middleware exited method
for (const router of this.routers) {
const routes = router.visited();
for (const route of routes) {
if (route.middleware.exited) {
route.middleware.exited(route.visited);
route.visited = null;
}
}
}
}
_callMiddlewareFailed(currentRoutes, request, response) {
for (const route of currentRoutes) {
// calls middleware failed method
if (route.middleware.failed) {
route.middleware.failed(request, response);
if (route.middleware.next && !route.middleware.next()) {
break;
}
} else {
// calls middleware method
let breakMiddlewareLoop = true;
const next = () => {
breakMiddlewareLoop = false;
};
route.middleware(request, response, next);
if (breakMiddlewareLoop) {
break;
}
}
}
}
///////////////////////////////////////////////////////
// Ajax request
_fetch({method, uri, headers, data}, resolve, reject) {
// calls middleware exited method
this._callMiddlewareExited();
this._callMiddlewareMethod('exited');
// gathers all routes impacted by the uri
const currentRoutes = this._routes(uri, method);
const currentRoutes = this._routes(req);
// calls middleware entered method
this._callMiddlewareEntered(currentRoutes, {method, uri, headers, data});
this._callMiddlewareMethod('entered', currentRoutes, req);
// invokes http request
this.requester.fetch({uri, method},
this.settings.get('http requester').fetch(req,
(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) {
resolve(request, response);
}
},
(request, response) => {
this._callMiddlewareFailed(currentRoutes, request, response);
this._callMiddlewareMethod('failed', currentRoutes, request, response);
if (reject) {
reject(request, response);
}
@ -200,37 +294,71 @@ export default class Application {
}
}
Object.keys(HTTP_METHODS).reduce((reqProto, method) => {
// Middleware methods
const middlewareMethodeName = method.toLowerCase();
reqProto[middlewareMethodeName] = function(...args) {
if (args.length === 0) {
throw new TypeError(`${middlewareMethodeName} method takes at least a middleware`);
HTTP_METHODS.reduce((reqProto, 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 "/"
* 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();
reqProto[middlewareMethodName] = function(...args) {
let {baseUri, middleware, which} = toParameters(args);
if (middlewareMethodName === 'get' && typeof which === 'string') {
return this.settings.get(which);
}
let baseUri, middleware, which;
if (args.length === 1) {
[which,] = args;
} else {
[baseUri, which,] = args;
if (!middleware) {
throw new TypeError(`method takes a middleware ${middlewareMethodName === 'get' ? 'or a string' : ''}`);
}
if (!(which instanceof Middleware) && (typeof which !== 'function')) {
throw new TypeError(`${middlewareMethodeName} method takes at least a middleware`);
}
const router = new Router();
middleware = which;
router[middlewareMethodeName](baseUri, middleware);
router[middlewareMethodName](baseUri, middleware);
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();
reqProto[httpMethodName] = function(request, resolve, reject) {
let {uri, headers, data} = request;
let {uri, headers, data, history} = request;
if (!uri) {
uri = request;
}
@ -238,9 +366,27 @@ Object.keys(HTTP_METHODS).reduce((reqProto, method) => {
uri,
method,
headers,
data
data,
history
}, resolve, reject);
};
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 Router from './router';
import Middleware from './middleware';
/**
* Create a frontexpress application.
*
* @return {Function}
* @api public
*/
const frontexpress = () => new Application();
/**
* Expose Router, Middleware constructors.
*/
frontexpress.Router = (baseUri) => new Router(baseUri);
frontexpress.Middleware = (name) => new Middleware(name);
frontexpress.Middleware = Middleware;
export default frontexpress;

8
lib/methods.js Normal file
View File

@ -0,0 +1,8 @@
/**
* 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 {
/**
* Middleware initialization
*
* @param {String} middleware name
*/
constructor(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() {
return true;

View File

@ -1,94 +1,48 @@
export const HTTP_METHODS = {
'GET': {
uri({uri, headers, data}) {
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;
}
},
'POST': {
headers({uri, headers, data}) {
const postHeaders = {};
postHeaders['Content-type'] = 'application/x-www-form-urlencoded';
if (headers) {
Object.keys(headers).reduce((phds, headKey) => {
phds[headKey] = headers[headKey];
return phds;
}, postHeaders);
}
return postHeaders;
},
data({uri, headers, data}) {
if (!data) {
return data;
}
return Object.keys(data).reduce((newData, d, index) => {
if (index !== 0) {
newData += '&';
}
newData += `${d}=${data[d]}`;
return newData;
}, '');
}
},
'PUT': {
//TODO
},
'DELETE': {
//TODO
}
// non exhaustive list
};
/**
* Module dependencies.
* @private
*/
export default class Requester {
fetch({method, uri, headers, data}, resolve, reject) {
const transformer = HTTP_METHODS[method];
uri = transformer.uri ? transformer.uri({uri, headers, data}) : uri;
headers = transformer.headers ? transformer.headers({uri, headers, data}) : headers;
data = transformer.data ? transformer.data({uri, headers, data}) : data;
/**
* 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) => {
resolve(
{method, uri, headers, data},
{status: 200, statusText: 'OK', responseText}
request,
{
status: 200,
statusText: 'OK',
responseText
}
);
};
const fail = ({status, statusText, errorThrown}) => {
const errors = this._analyzeErrors({status, statusText, errorThrown});
reject(
{method, uri, headers, data},
{status, statusText, errorThrown, errors}
request,
{
status,
statusText,
errorThrown,
errors: `HTTP ${status} ${statusText?statusText:''}`
}
);
};
const xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = () => {
if (xmlhttp.readyState === 4) {
if (xmlhttp.readyState === 4) { //XMLHttpRequest.DONE
if (xmlhttp.status === 200) {
success(xmlhttp.responseText);
} else {
@ -99,9 +53,9 @@ export default class Requester {
try {
xmlhttp.open(method, uri, true);
if (headers) {
for (const header of Object.keys(headers)) {
Object.keys(headers).forEach((header) => {
xmlhttp.setRequestHeader(header, headers[header]);
}
});
}
if (data) {
xmlhttp.send(data);
@ -112,32 +66,35 @@ export default class Requester {
fail({errorThrown});
}
}
}
_analyzeErrors(response) {
let error = '';
if (response.status === 0) {
error = 'Server access problem. Check your network connection';
} else if (response.status === 401) {
error = 'Your session has expired, Please reconnect. [code: 401]';
} else if (response.status === 404) {
error = 'Page not found on server. [code: 404]';
} else if (response.status === 500) {
error = 'Internal server error. [code: 500]';
} else if (response.errorThrown) {
if (response.errorThrown.name === 'SyntaxError') {
error = 'Problem during data decoding [JSON]';
} else if (response.errorThrown.name === 'TimeoutError') {
error = 'Server is taking too long to reply';
} else if (response.errorThrown.name === 'AbortError') {
error = 'Request cancelled on server';
} else if (response.errorThrown.name === 'NetworkError') {
error = 'A network error occurred';
} else {
error = `${response.errorThrown.name} ${response.errorThrown.message}`;
}
} else {
error = `Unknown error. ${response.statusText?response.statusText:''}`;
export const httpGetTransformer = {
uri({uri, headers, data}) {
if (!data) {
return uri;
}
return error;
let [uriWithoutAnchor, anchor] = [uri, ''];
const match = /^(.*)(#.*)$/.exec(uri);
if (match) {
[,uriWithoutAnchor, anchor] = /^(.*)(#.*)$/.exec(uri);
}
uriWithoutAnchor = Object.keys(data).reduce((gUri, d, index) => {
gUri += `${(index === 0 && gUri.indexOf('?') === -1)?'?':'&'}${d}=${data[d]}`;
return gUri;
}, uriWithoutAnchor);
return uriWithoutAnchor + anchor;
}
}
};
// export const httpPostTransformer = {
// headers({uri, headers, data}) {
// if (!data) {
// return headers;
// }
// const updatedHeaders = headers || {};
// if (!updatedHeaders['Content-Type']) {
// updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
// }
// return updatedHeaders;
// }
// };

View File

@ -1,7 +1,27 @@
import {HTTP_METHODS} from './requester';
/**
* Module dependencies.
* @private
*/
import HTTP_METHODS from './methods';
import {toParameters} from './application';
import Middleware from './middleware';
class Route {
/**
* Route object.
* @private
*/
export class Route {
/**
* Initialize the route.
*
* @private
*/
constructor(router, uriPart, method, middleware) {
this.router = router;
this.uriPart = uriPart;
@ -10,6 +30,13 @@ class Route {
this.visited = false;
}
/**
* Return route's uri.
*
* @private
*/
get uri() {
if (!this.uriPart && !this.method) {
return undefined;
@ -23,26 +50,46 @@ class Route {
return this.router.baseUri;
}
if (this.router.baseUri && this.uriPart) {
return (this.router.baseUri.trim() + this.uriPart.trim()).replace(/\/{2,}/, '/');
}
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;
}
}
/**
* Router object.
* @public
*/
const error_middleware_message = 'method takes at least a middleware';
export default class Router {
/**
* Initialize the router.
*
* @private
*/
constructor(uri) {
if (uri) {
this._baseUri = uri;
}
this._baseUri = uri;
this._routes = [];
}
/**
* Do some checks and set _baseUri.
*
* @private
*/
set baseUri(uri) {
if (!uri) {
return;
@ -53,68 +100,79 @@ export default class Router {
return;
}
if (this._baseUri instanceof RegExp) {
throw new TypeError(`the router already contains a regexp uri ${this._baseUri.toString()} It cannot be mixed with ${uri.toString()}`);
}
if (uri instanceof RegExp) {
throw new TypeError(`the router already contains an uri ${this._baseUri.toString()} It cannot be mixed with regexp ${uri.toString()}`);
if (typeof this._baseUri !== typeof uri) {
throw new TypeError('router cannot mix regexp and uri');
}
}
/**
* Return router's _baseUri.
*
* @private
*/
get baseUri() {
return this._baseUri;
}
/**
* Add a route to the router.
*
* @private
*/
_add(route) {
this._routes.push(route);
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) => {
if (!route.uri && !route.method) {
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;
return isRouteMatch(request, route);
});
}
/**
* Gather visited routes from routers.
*
* @private
*/
visited() {
return this._routes.filter((route) => {
return route.visited;
});
return this._routes.filter(route => 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) {
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));
@ -122,53 +180,143 @@ export default class Router {
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) {
if (args.length === 0) {
throw new TypeError('use all method takes at least a middleware');
}
let middleware;
if (args.length === 1) {
[middleware,] = args;
} else {
[, middleware,] = args;
const {middleware} = toParameters(args);
if (!middleware) {
throw new TypeError(error_middleware_message);
}
if (!(middleware instanceof Middleware) && (typeof middleware !== 'function') ) {
throw new TypeError('use all method takes at least a middleware');
}
for (const method of Object.keys(HTTP_METHODS)) {
HTTP_METHODS.forEach((method) => {
this[method.toLowerCase()](...args);
}
});
return this;
}
}
for (const method of Object.keys(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();
Router.prototype[methodName] = function(...args) {
if (args.length === 0) {
throw new TypeError(`use ${methodName} method takes at least a middleware`);
}
let uri, middleware;
if (args.length === 1) {
[middleware,] = args;
} else {
[uri, middleware,] = args;
const {baseUri, middleware} = toParameters(args);
if (!middleware) {
throw new TypeError(error_middleware_message);
}
if (!(middleware instanceof Middleware) && (typeof middleware !== 'function') ) {
throw new TypeError(`use ${methodName} method takes at least a middleware`);
if (baseUri && this._baseUri && this._baseUri instanceof RegExp) {
throw new TypeError('router cannot mix uri/regexp');
}
if (uri && this._baseUri && this._baseUri instanceof RegExp) {
throw new TypeError('router contains a regexp cannot mix with route uri/regexp');
}
this._add(new Route(this, uri, method, middleware));
this._add(new Route(this, baseUri, method, middleware));
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;
}

83
lib/settings.js Normal file
View File

@ -0,0 +1,83 @@
/**
* 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
*/
constructor() {
// default settings
this.settings = {
'http requester': new Requester(),
'http GET transformer': httpGetTransformer,
// 'http POST transformer': httpPostTransformer,
'route matcher': routeMatcher
};
this.rules = {
'http requester': (requester) => {
errorIfNotFunction(requester.fetch , 'setting http requester has no fetch function');
},
'http GET transformer': (transformer) => {
if (!transformer || (!transformer.uri && !transformer.headers && !transformer.data)) {
throw new TypeError('setting http transformer one of functions: uri, headers, data is missing');
}
},
'route matcher': (routeMatcher) => {
errorIfNotFunction(routeMatcher, 'setting route matcher is not a function');
}
};
}
/**
* Assign `setting` to `val`
*
* @param {String} setting
* @param {*} [val]
* @private
*/
set(name, value) {
const checkRules = this.rules[name];
if (checkRules) {
checkRules(value);
}
this.settings[name] = value;
}
/**
* Return `setting`'s value.
*
* @param {String} setting
* @private
*/
get(name) {
return this.settings[name];
}
};

View File

@ -1,18 +1,21 @@
{
"name": "frontexpress",
"version": "0.1.0",
"description": "Minimalist front end router framework a la express",
"version": "1.2.0",
"description": "Frontexpress manages routes in browser like ExpressJS on Node",
"main": "dist/frontexpress.js",
"jsnext:main": "lib/frontexpress.js",
"scripts": {
"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",
"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)",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
"node": ">=4.2.6"
},
"repository": "camelaissani/frontexpress",
"keywords": [
@ -21,26 +24,38 @@
"web",
"router",
"middleware",
"rest",
"restful",
"app",
"api",
"express",
"frontexpress"
],
"devDependencies": {
"babel-cli": "^6.10.1",
"babel-core": "^6.10.4",
"babel-eslint": "^6.1.0",
"babel-istanbul": "^0.8.0",
"babel-preset-es2015": "^6.9.0",
"chai": "^3.5.0",
"eslint": "^2.11.0",
"eslint-loader": "^1.3.0",
"fake-xml-http-request": "^1.4.0",
"istanbul": "^0.4.3",
"mocha": "^2.5.3",
"rimraf": "^2.5.2",
"sinon": "^1.17.4"
}
"babel-cli": "^6.18.0",
"babel-core": "^6.21.0",
"babel-eslint": "^7.1.1",
"babel-istanbul": "^0.12.1",
"babel-preset-es2015": "^6.18.0",
"babel-preset-es2015-rollup": "^3.0.0",
"babel-register": "^6.18.0",
"bytesize": "^0.2.0",
"chai": "^4.*",
"eslint": "^3.*",
"istanbul": "^0.4.5",
"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*/
import chai, {assert} from 'chai';
import sinon from 'sinon';
//import jsdom from 'jsdom';
import frontexpress from '../lib/frontexpress';
import Requester from '../lib/requester';
describe('Application', () => {
class MyMiddleware extends frontexpress.Middleware {
entered() {}
exited() {}
failed() {}
updated() {}
}
let requester;
describe('generated methods', () => {
@ -25,18 +33,108 @@ describe('Application', () => {
});
});
describe('listen method', () => {
let eventFn = {};
// // JSDOM cannot manage pushState/onpopstate/window.location
// 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(() => {
const browserHistory = [{uri: '/'}];
let browserHistoryIndex = 0;
global.document = {};
global.window = {
addEventListener(eventType, callback) {
eventFn[eventType] = callback;
},
location: {
pathname: '/route1',
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) => {
const app = frontexpress();
const m = frontexpress.Middleware();
const m = new MyMiddleware();
sinon.stub(m, 'entered', () => {
done();
});
@ -70,7 +168,7 @@ describe('Application', () => {
it('with middleware object readyState===interactive', (done) => {
const app = frontexpress();
const m = frontexpress.Middleware();
const m = new MyMiddleware();
sinon.stub(m, 'updated', () => {
done();
});
@ -85,7 +183,7 @@ describe('Application', () => {
it('with middleware object event beforeunload', (done) => {
const app = frontexpress();
const m = frontexpress.Middleware();
const m = new MyMiddleware();
sinon.stub(m, 'exited', () => {
done();
});
@ -93,26 +191,131 @@ describe('Application', () => {
app.use('/route1', m);
app.listen(() => {
//simulate beforeunload
eventFn['beforeunload']();
window.onbeforeunload();
});
//simulate readystatechange
document.readyState = 'interactive';
document.onreadystatechange();
});
});
describe('set method', () => {
it('unsupported setting', () => {
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();
chai.expect(() => app.set('blabla', 'value')).to.throw(ReferenceError);
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('supported setting', () => {
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', () => {
it('custom setting', () => {
const app = frontexpress();
app.set('blabla', 'value');
assert(app.get('blabla') === 'value');
});
it('core setting', () => {
const requester = new Requester();
const app = frontexpress();
app.set('http-requester', requester);
assert(app.requester === requester);
app.set('http requester', requester);
assert(app.get('http requester') === requester);
assert(app.set('http requester') === requester);
});
it('bad core setting', () => {
const app = frontexpress();
chai.expect(() => (app.set('http requester', 'not an object with fetch function'))).to.throw(TypeError);
});
});
@ -129,25 +332,25 @@ describe('Application', () => {
it('no arguments', () => {
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
assert.throws(app.use, TypeError);
});
it('bad arguments', () => {
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
chai.expect(() => app.use('eee')).to.throw(TypeError);
});
it('mixing uri and regexp', () => {
let router = frontexpress.Router('/subroute');
let app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
chai.expect(() => app.use(/route/, router)).to.throw(TypeError);
router = frontexpress.Router(/subroute/);
app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
chai.expect(() => app.use('/route', router)).to.throw(TypeError);
});
@ -155,7 +358,7 @@ describe('Application', () => {
const spy = sinon.spy();
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.use((request, response, next) => {spy();});
app.httpGet('/', (request, response) => {
@ -172,7 +375,7 @@ describe('Application', () => {
const spy = sinon.spy();
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.use('/route1', (request, response, next) => {spy();});
app.httpGet('/route1', (request, response) => {
@ -197,7 +400,7 @@ describe('Application', () => {
const spy = sinon.spy();
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.use((request, response, next) => {spy();});
app.httpGet('/', null, (request, response) => {
@ -208,11 +411,11 @@ describe('Application', () => {
});
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 app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.use(middleware);
app.httpGet('/', (request, response) => {
@ -226,11 +429,11 @@ describe('Application', () => {
});
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 app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.use('/route1', middleware);
app.httpGet('/route1', (request, response) => {
@ -252,11 +455,11 @@ describe('Application', () => {
);
});
const middleware = frontexpress.Middleware('on path /');
const middleware = new MyMiddleware('on path /');
const spy = sinon.spy(middleware, 'failed');
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.use(middleware);
app.httpGet('/', null, (request, response) => {
@ -274,7 +477,7 @@ describe('Application', () => {
.post((request, response, next) => {spy();});
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.use(router);
app.httpGet('/', (request, response) => {
@ -295,7 +498,7 @@ describe('Application', () => {
.post((request, response, next) => {spy();});
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.use('/route1', router);
app.httpGet('/route1', (request, response) => {
@ -309,11 +512,11 @@ describe('Application', () => {
});
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 app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
const router = frontexpress.Router();
router.get('/subroute1', middleware);
@ -332,12 +535,12 @@ describe('Application', () => {
it('use two routers', (done)=> {
const router1 = frontexpress.Router();
const m1 = frontexpress.Middleware();
const m1 = new MyMiddleware();
const spy1 = sinon.spy(m1, 'updated');
router1.get('/subroute1', m1);
const router2 = frontexpress.Router();
const m2 = frontexpress.Middleware();
const m2 = new MyMiddleware();
const spy2 = sinon.spy(m2, 'updated');
router2.get('/subroute2', m2);
@ -345,7 +548,7 @@ describe('Application', () => {
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.use(/^\/route1/, router1);
app.use(/^\/route2/, router2);
@ -379,14 +582,13 @@ describe('Application', () => {
it('no arguments', () => {
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
assert.throws(app.get, TypeError);
});
it('bad arguments', () => {
const app = frontexpress();
app.set('http-requester', requester);
chai.expect(() => app.get('eee')).to.throw(TypeError);
app.set('http requester', requester);
chai.expect(() => app.get(frontexpress.Router())).to.throw(TypeError);
chai.expect(() => app.get('/route1', frontexpress.Router())).to.throw(TypeError);
});
@ -395,7 +597,7 @@ describe('Application', () => {
const spy = sinon.spy();
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.get((request, respons, next) => {spy();});
app.httpGet('/', (request, response) => {
@ -412,7 +614,7 @@ describe('Application', () => {
const spy = sinon.spy();
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.get('/route1', (request, response, next) => {spy();});
app.httpGet('/route1', (request, response) => {
@ -426,11 +628,11 @@ describe('Application', () => {
});
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 app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.get(middleware);
app.httpGet('/', (request, response) => {
@ -444,11 +646,11 @@ describe('Application', () => {
});
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 app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.get(middleware);
app.httpGet('/route1', (request, response) => {
@ -462,6 +664,24 @@ describe('Application', () => {
});
});
describe('post method', () => {
beforeEach(()=>{
requester = new Requester();
sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => {
resolve(
{uri, method, headers, data},
{status: 200, statusText: 'OK', responseText:''}
);
});
});
it('no arguments', () => {
const app = frontexpress();
app.set('http requester', requester);
assert.throws(app.post, TypeError);
});
});
describe('route method', () => {
beforeEach(()=>{
requester = new Requester();
@ -477,7 +697,7 @@ describe('Application', () => {
const spy = sinon.spy();
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.route().get((request, response, next) => {spy();});
app.httpGet('/', (request, response) => {
@ -494,7 +714,7 @@ describe('Application', () => {
const spy = sinon.spy();
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.route('/').get((request, response, next) => {spy();});
app.httpGet('/', (request, response) => {
@ -511,7 +731,7 @@ describe('Application', () => {
const spy = sinon.spy();
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.route('/route1').get((request, response, next) => {spy();});
app.httpGet('/route1', (request, response) => {
@ -528,7 +748,7 @@ describe('Application', () => {
const spy = sinon.spy();
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.route().get('/subroute1', (request, response, next) => {spy();});
app.httpGet('/subroute1', (request, response) => {
@ -544,7 +764,7 @@ describe('Application', () => {
const spy = sinon.spy();
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.route('/').get('/subroute1', (request, response, next) => {spy();});
app.httpGet('/subroute1', (request, response) => {
@ -560,7 +780,7 @@ describe('Application', () => {
const spy = sinon.spy();
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.route('/route1').get('/subroute1', (request, response, next) => {spy();});
app.httpGet('/route1/subroute1', (request, response) => {
@ -586,9 +806,9 @@ describe('Application', () => {
it('http GET request', (done) => {
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_updated = sinon.spy(getMiddleware, 'updated');
const spy_get_exited = sinon.spy(getMiddleware, 'exited');
@ -614,19 +834,19 @@ describe('Application', () => {
it('http GET followed by http POST requests', (done) => {
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_updated = sinon.spy(allMiddleware, 'updated');
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_updated = sinon.spy(getMiddleware, 'updated');
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_updated = sinon.spy(postMiddleware, 'updated');
const spy_post_exited = sinon.spy(postMiddleware, 'exited');
@ -697,9 +917,9 @@ describe('Application', () => {
);
});
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');
@ -716,12 +936,12 @@ describe('Application', () => {
it('next method', (done) => {
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
const m1 = frontexpress.Middleware('m1');
const m2 = frontexpress.Middleware('m2');
const m1 = new MyMiddleware('m1');
const m2 = new MyMiddleware('m2');
m2.next = () => false;
const m3 = frontexpress.Middleware('m3');
const m3 = new MyMiddleware('m3');
const spy_m1 = sinon.spy(m1, 'updated');
const spy_m2 = sinon.spy(m2, 'updated');
@ -750,7 +970,7 @@ describe('Application', () => {
it('next method', (done) => {
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
const m1 = (req, res, next) => {next();};
const m2 = (req, res, next) => {};
@ -769,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', () => {
assert(frontexpress.Middleware);
assert(frontexpress.Middleware() instanceof Middleware);
assert(new frontexpress.Middleware() instanceof Middleware);
const m1 = frontexpress.Middleware();
const m2 = frontexpress.Middleware();
const m1 = new frontexpress.Middleware();
const m2 = new frontexpress.Middleware();
assert(m1 !== m2);
});
@ -33,4 +33,4 @@ describe('frontexpress', () => {
const app2 = frontexpress();
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

@ -50,7 +50,7 @@ describe('Test sample from README', () => {
const h3 = (req, res, next) => { spy_log('h3!'); next(); };
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.get('/example/a', h1);
app.get('/example/a', h2);
@ -77,7 +77,7 @@ describe('Test sample from README', () => {
const spy_log = sinon.spy();
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.route('/book')
.get((req, res) => { spy_log('Get a random book');})
@ -111,7 +111,7 @@ describe('Test sample from README', () => {
const router = frontexpress.Router();
// middleware that is specific to this router
// middleware which is specific to this router
router.use((req, res, next) => {
spy_log(`Time: ${Date.now()}`);
next();
@ -128,7 +128,7 @@ describe('Test sample from README', () => {
});
const app = frontexpress();
app.set('http-requester', requester);
app.set('http requester', requester);
app.use('/birds', router);
// make an ajax request on /birds
@ -148,4 +148,4 @@ describe('Test sample from README', () => {
});
});
});
});

View File

@ -1,35 +1,8 @@
/*eslint-env mocha*/
/*global global*/
import {assert} from 'chai';
import chai, {assert} from 'chai';
import sinon from 'sinon';
import FakeXMLHttpRequest from 'fake-xml-http-request';
import Requester, {HTTP_METHODS} from '../lib/requester';
describe('HTTP_METHODS', () => {
it('GET method simple uri', () => {
const uriFn = HTTP_METHODS['GET'].uri;
const dataFn = HTTP_METHODS['GET'].data;
assert(uriFn({uri: '/route', data:{a:'b', c:'d'}}) === '/route?a=b&c=d');
assert(dataFn({uri: '/route', data:{a:'b', c:'d'}}) === undefined);
});
it('GET method uri with query string', () => {
const uriFn = HTTP_METHODS['GET'].uri;
const dataFn = HTTP_METHODS['GET'].data;
assert(uriFn({uri: '/route?x=y&z=a', data:{a:'b', c:'d'}}) === '/route?x=y&z=a&a=b&c=d');
assert(dataFn({uri: '/route?x=y&z=a', data:{a:'b', c:'d'}}) === undefined);
});
it('GET method uri with query string and anchor', () => {
const uriFn = HTTP_METHODS['GET'].uri;
const dataFn = HTTP_METHODS['GET'].data;
assert(uriFn({uri: '/route?x=y&z=a#anchor1', data:{a:'b', c:'d'}}) === '/route?x=y&z=a&a=b&c=d#anchor1');
assert(dataFn({uri: '/route?x=y&z=a#anchor1', data:{a:'b', c:'d'}}) === undefined);
});
});
import Requester from '../lib/requester';
describe('Requester', () => {
function xHttpWillRespond(xhttp, readyState, status, statusText, responseText) {
@ -51,13 +24,17 @@ describe('Requester', () => {
function xHttpWillThrow(xhttp, sendErrorName, openErrorName) {
const stub_send = sinon.stub(xhttp, 'send', function() {
if (sendErrorName) {
throw {name: sendErrorName};
const e = new Error(sendErrorName);
e.name = sendErrorName;
throw e;
}
});
const stub_open = sinon.stub(xhttp, 'open', function() {
if (openErrorName) {
throw {name: openErrorName};
const e = new Error(openErrorName);
e.name = openErrorName;
throw e;
}
});
@ -67,8 +44,11 @@ describe('Requester', () => {
let xhttp;
beforeEach(() => {
// Stub XMLHttpRequest
xhttp = new FakeXMLHttpRequest();
xhttp = {
setRequestHeader(){},
open(){},
send(){}
};
global.XMLHttpRequest = () => {
return xhttp;
};
@ -79,15 +59,27 @@ describe('Requester', () => {
it('with data', (done) => {
const requester = new Requester();
const {stub_open, stub_send} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
requester.fetch({method: 'GET', uri:'/route1', data:{p1: 'a', p2: 'b', p3: 'c'}},
(request, response) => {
assert(request.uri === '/route1?p1=a&p2=b&p3=c');
assert(request.method === 'GET');
assert(request.uri === '/route1');
assert(request.data !== undefined);
assert(request.data.p1 === 'a');
assert(request.data.p2 === 'b');
assert(request.data.p3 === 'c');
assert(response.status === 200);
assert(response.statusText === 'OK');
assert(response.responseText === '<p>content!</p>');
assert(response.errorThrown === undefined);
assert(response.errors === undefined);
done();
},
(err) => {
done(err);
(request, response) => {
done(response.error);
});
});
@ -101,8 +93,8 @@ describe('Requester', () => {
assert(stub_send.calledOnce);
assert(stub_open.calledBefore(stub_send));
assert(request.uri === '/route1');
assert(request.method === 'GET');
assert(request.uri === '/route1');
assert(request.data === undefined);
assert(response.status === 200);
@ -113,8 +105,8 @@ describe('Requester', () => {
done();
},
(error) => {
done(error);
(request, response) => {
done(response.error);
});
});
});
@ -126,7 +118,7 @@ describe('Requester', () => {
const {stub_open, stub_send, stub_setRequestHeader} = xHttpWillRespond(xhttp, 4, 200, '', '<p>content!</p>');
requester.fetch({method: 'POST', uri:'/route1', data:{p1: 'a', p2: 'b', p3: 'c'}},
requester.fetch({method: 'POST', uri:'/route1', headers:{'head1':'value1'}, data:{p1: 'a', p2: 'b', p3: 'c'}},
(request, response) => {
assert(stub_open.calledOnce);
assert(stub_setRequestHeader.calledOnce);
@ -135,13 +127,23 @@ describe('Requester', () => {
assert(stub_setRequestHeader.calledAfter(stub_open));
assert(stub_setRequestHeader.calledBefore(stub_send));
assert(request.method === 'POST');
assert(request.uri === '/route1');
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
assert(request.data === 'p1=a&p2=b&p3=c');
assert(request.data !== undefined);
assert(request.data.p1 === 'a');
assert(request.data.p2 === 'b');
assert(request.data.p3 === 'c');
assert(response.status === 200);
assert(response.statusText === 'OK');
assert(response.responseText === '<p>content!</p>');
assert(response.errorThrown === undefined);
assert(response.errors === undefined);
done();
},
(err) => {
done(err);
(request, response) => {
done(response.error);
});
});
@ -153,19 +155,18 @@ describe('Requester', () => {
requester.fetch({method: 'POST', uri:'/route1'},
(request, response) => {
assert(stub_open.calledOnce);
assert(stub_setRequestHeader.calledOnce);
assert(stub_setRequestHeader.callCount === 0);
assert(stub_send.calledOnce);
assert(stub_open.calledBefore(stub_send));
assert(stub_setRequestHeader.calledAfter(stub_open));
assert(stub_setRequestHeader.calledBefore(stub_send));
assert(request.method === 'POST');
assert(request.uri === '/route1');
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
assert(request.data === undefined);
done();
},
(err) => {
done(err);
(request, response) => {
done(response.error);
});
});
@ -177,20 +178,20 @@ describe('Requester', () => {
requester.fetch({method: 'POST', uri:'/route1', headers: {'Accept-Charset': 'utf-8'}},
(request, response) => {
assert(stub_open.calledOnce);
assert(stub_setRequestHeader.calledTwice);
assert(stub_setRequestHeader.calledOnce);
assert(stub_send.calledOnce);
assert(stub_open.calledBefore(stub_send));
assert(stub_setRequestHeader.calledAfter(stub_open));
assert(stub_setRequestHeader.calledBefore(stub_send));
assert(request.method === 'POST');
assert(request.uri === '/route1');
assert(request.headers['Content-type'] === 'application/x-www-form-urlencoded');
assert(request.headers['Accept-Charset'] === 'utf-8');
assert(request.data === undefined);
done();
},
(err) => {
done(err);
(request, response) => {
done(response.error);
});
});
});
@ -377,24 +378,16 @@ describe('Requester', () => {
});
});
it('request returns unknown error', (done) => {
const requester = new Requester();
// Removed for reducing size of frontexpress
// 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');
requester.fetch({method: 'GET', uri:'/route1'}, null,
(request, response) => {
assert(stub_send.calledOnce);
assert(stub_open.calledOnce);
// chai.expect(() => {
// requester.fetch({method: 'GET', uri:'/route1'});
// }).to.throw(/BlaBlaError/);
assert(response.status === undefined);
assert(response.statusText === undefined);
assert(response.responseText === undefined);
assert(response.errorThrown.name === 'BlaBlaError');
assert(response.errors.length !== 0);
done();
});
});
// });
});
});

View File

@ -2,7 +2,10 @@
import chai, {assert} from 'chai';
import sinon from 'sinon';
import frontexpress from '../lib/frontexpress';
import {HTTP_METHODS} from '../lib/requester';
import HTTP_METHODS from '../lib/methods';
const application = frontexpress();
const routeMatcher = application.get('route matcher');
describe('Router', () => {
@ -12,11 +15,22 @@ describe('Router', () => {
assert(typeof router.all === 'function');
assert(typeof router.get === 'function');
assert(typeof router.put === 'function');
assert(typeof router.patch === 'function');
assert(typeof router.post === 'function');
assert(typeof router.delete === 'function');
});
});
describe('use method', () => {
const router = frontexpress.Router();
chai.expect(() => {
router.use();
}).to.throw(TypeError);
chai.expect(() => {
router.use('dddd');
}).to.throw(TypeError);
});
describe('visited method', () => {
it('get visited route', ()=> {
const router = frontexpress.Router();
@ -36,7 +50,7 @@ describe('Router', () => {
router.get(middleware);
const r1 = router.routes('/', 'GET');
const r1 = router.routes(application, {uri: '/', method: 'GET'});
assert(r1.length === 1);
assert(r1[0].uri === undefined);
assert(r1[0].method === 'GET');
@ -54,37 +68,37 @@ describe('Router', () => {
.post('/route2', middleware2)
.all('/route3', middleware3);
const r1 = router.routes('/route1', 'GET');
const r1 = router.routes(application, {uri: '/route1', method: 'GET'});
assert(r1.length === 1);
assert(r1[0].uri === '/route1');
assert(r1[0].method === 'GET');
assert(r1[0].middleware === middleware1);
const r2 = router.routes('/route2', 'POST');
const r2 = router.routes(application, {uri: '/route2', method: 'POST'});
assert(r2.length === 1);
assert(r2[0].uri === '/route2');
assert(r2[0].method === 'POST');
assert(r2[0].middleware === middleware2);
let r3 = router.routes('/route3', 'GET');
let r3 = router.routes(application, {uri: '/route3', method: 'GET'});
assert(r3.length === 1);
assert(r3[0].uri === '/route3');
assert(r3[0].method === 'GET');
assert(r3[0].middleware === middleware3);
r3 = router.routes('/route3', 'POST');
r3 = router.routes(application, {uri: '/route3', method: 'POST'});
assert(r3.length === 1);
assert(r3[0].uri === '/route3');
assert(r3[0].method === 'POST');
assert(r3[0].middleware === middleware3);
r3 = router.routes('/route3', 'PUT');
r3 = router.routes(application, {uri: '/route3', method: 'PUT'});
assert(r3.length === 1);
assert(r3[0].uri === '/route3');
assert(r3[0].method === 'PUT');
assert(r3[0].middleware === middleware3);
r3 = router.routes('/route3', 'DELETE');
r3 = router.routes(application, {uri: '/route3', method: 'DELETE'});
assert(r3.length === 1);
assert(r3[0].uri === '/route3');
assert(r3[0].method === 'DELETE');
@ -93,11 +107,11 @@ describe('Router', () => {
it('no root path and regexp uri', ()=> {
const router = frontexpress.Router();
const middleware = frontexpress.Middleware();
const middleware = new frontexpress.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[0].uri instanceof RegExp);
assert(r[0].uri.toString() === new RegExp('^\/route1').toString());
@ -108,9 +122,9 @@ describe('Router', () => {
it('with root path /route1 and path /subroute', () => {
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[0].uri === '/route1/subroute');
});
@ -118,9 +132,9 @@ describe('Router', () => {
it('with root path /route1 and no path uri', () => {
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[0].uri === '/route1');
});
@ -128,9 +142,9 @@ describe('Router', () => {
it('duplicate / in route', () => {
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[0].uri === '/route1/subroute');
});
@ -138,9 +152,9 @@ describe('Router', () => {
it('spaces in route', () => {
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[0].uri === '/route1/subroute');
@ -148,9 +162,9 @@ describe('Router', () => {
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[0].uri === '/route1');
});
@ -158,9 +172,9 @@ describe('Router', () => {
it('route with query string', () => {
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[0].uri === '/route1/subroute');
assert(r[0].data === undefined);
@ -169,9 +183,9 @@ describe('Router', () => {
it('route with anchor', () => {
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[0].uri === '/route1/subroute');
assert(r[0].data === undefined);
@ -180,9 +194,9 @@ describe('Router', () => {
it('route with query string and anchor', () => {
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[0].uri === '/route1/subroute');
assert(r[0].data === undefined);
@ -204,10 +218,10 @@ describe('Router', () => {
it('only middleware as argument', () => {
const router = frontexpress.Router('/route1');
const middleware = frontexpress.Middleware();
const middleware = new frontexpress.Middleware();
const spied_methods = [];
for (const method of Object.keys(HTTP_METHODS)) {
for (const method of HTTP_METHODS) {
spied_methods.push(sinon.spy(router, method.toLowerCase()));
}
@ -220,10 +234,10 @@ describe('Router', () => {
it('with path /route1 and middleware as arguments', () => {
const router = frontexpress.Router();
const middleware = frontexpress.Middleware();
const middleware = new frontexpress.Middleware();
const spied_methods = [];
for (const method of Object.keys(HTTP_METHODS)) {
for (const method of HTTP_METHODS) {
spied_methods.push(sinon.spy(router, method.toLowerCase()));
}
@ -250,11 +264,11 @@ describe('Router', () => {
it('only middleware as argument', () => {
const router = frontexpress.Router('/');
const middleware = frontexpress.Middleware();
const middleware = new frontexpress.Middleware();
router.get(middleware);
const r = router.routes('/', 'GET');
const r = router.routes(application, {uri: '/', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/');
assert(r[0].method === 'GET');
@ -263,11 +277,11 @@ describe('Router', () => {
it('with path /route1 and middleware as arguments', () => {
const router = frontexpress.Router();
const middleware = frontexpress.Middleware();
const middleware = new frontexpress.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[0].uri === '/route1');
assert(r[0].method === 'GET');
@ -276,16 +290,16 @@ describe('Router', () => {
it('router with regexp and route with /route1', () => {
const router = frontexpress.Router(/^\//);
const middleware = frontexpress.Middleware();
const middleware = new frontexpress.Middleware();
chai.expect(() => router.get('/route1', middleware)).to.throw(TypeError);
});
it('router with regexp and route without uri', () => {
const router = frontexpress.Router(/^\/part/);
const middleware = frontexpress.Middleware();
const middleware = new frontexpress.Middleware();
router.get(middleware);
const r = router.routes('/part1', 'GET');
const r = router.routes(application, {uri: '/part1', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri instanceof RegExp);
assert(r[0].uri.toString() === new RegExp('^\/part').toString());
@ -293,4 +307,113 @@ describe('Router', () => {
assert(r[0].middleware === middleware);
});
});
describe('check route matcher', () => {
it('/', () => {
const route = {uri: '/', method: 'GET'};
const request = {uri: '/', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {});
});
it('/a/b/c', () => {
const route = {uri: '/a/b/c', method: 'GET'};
let request = {uri: '/a/b/c', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {});
request = {uri: '/a/b/c/', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
});
it('/^\//', () => {
const route = {uri: /^\//, method: 'GET'};
const request = {uri: '/a/b/c', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {});
});
it('/:id', () => {
const route = {uri: '/:id', method: 'GET'};
const request = {uri: '/1000', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.strictEqual(request.params.id, 1000);
});
it('/user/:id', () => {
const route = {uri: '/user/:id', method: 'GET'};
let request = {uri: '/user/1000', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.strictEqual(request.params.id, 1000);
request = {uri: '/user/100.2122', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.strictEqual(request.params.id, 100.2122);
request = {uri: '/user', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
request = {uri: '/user/', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
});
it('/user/:id with id as coma separated values', () => {
const route = {uri: '/user/:id', method: 'GET'};
let request = {uri: '/user/1,2,3', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {id: [1,2,3]});
request = {uri: '/user/1.5,2.55,4.25', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {id: [1.5,2.55,4.25]});
request = {uri: '/user/a,b,c', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {id: ['a','b','c']});
});
it('/user/:id?', () => {
const route = {uri: '/user/:id?', method: 'GET'};
let request = {uri: '/user/1000', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.strictEqual(request.params.id, 1000);
request = {uri: '/user', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), true);
assert.deepEqual(request.params, {id: undefined});
request = {uri: '/user/', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
});
it('/user/:firstname/:lastname', () => {
const route = {uri: '/user/:firstname/:lastname', method: 'GET'};
let request = {uri: '/user/camel/aissani', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {firstname: 'camel', lastname:'aissani'} );
request = {uri: '/user/camel', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
});
it('/user/:firstname?/:lastname', () => {
const route = {uri: '/user/:firstname?/:lastname', method: 'GET'};
let request = {uri: '/user/camel/aissani', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {firstname: 'camel', lastname:'aissani'} );
request = {uri: '/user/aissani', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {firstname: undefined, lastname:'aissani'} );
});
});
});

65
test/settings-test.js Normal file
View File

@ -0,0 +1,65 @@
/*eslint-env mocha*/
import chai, {assert} from 'chai';
import Settings from '../lib/settings';
describe('Settings', () => {
const settings = new Settings();
describe('http GET method transformer', () => {
it('check setting rule', () => {
const defaultHttpGetTransformer = settings.get('http GET transformer');
chai.expect(() => settings.set('http GET transformer', null)).to.throw(TypeError);
chai.expect(() => settings.set('http GET transformer', {})).to.throw(TypeError);
chai.expect(() => settings.set('http GET transformer', {foo:()=>{}})).to.throw(TypeError);
const uri = () => {};
settings.set('http GET transformer', {uri});
assert.deepEqual(settings.get('http GET transformer'), {uri});
const headers = () => {};
settings.set('http GET transformer', {headers});
assert.deepEqual(settings.get('http GET transformer'), {headers});
const data = () => {};
settings.set('http GET transformer', {data});
assert.deepEqual(settings.get('http GET transformer'), {data});
settings.set('http GET transformer', defaultHttpGetTransformer);
const defaultRouteMatcher = settings.get('route matcher');
chai.expect(() => settings.set('route matcher', null)).to.throw(TypeError);
chai.expect(() => settings.set('route matcher', {})).to.throw(TypeError);
chai.expect(() => settings.set('route matcher', 1)).to.throw(TypeError);
const routeMatcher = () => {};
settings.set('route matcher', routeMatcher);
assert.strictEqual(settings.get('route matcher'), routeMatcher);
});
it('simple uri', () => {
const uriFn = settings.get('http GET transformer').uri;
const dataFn = settings.get('http GET transformer').data;
assert(uriFn({uri: '/route', data:{a:'b', c:'d'}}) === '/route?a=b&c=d');
assert(dataFn === undefined);
});
it('uri with query string', () => {
const uriFn = settings.get('http GET transformer').uri;
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(dataFn === undefined);
});
it('uri with query string and anchor', () => {
const uriFn = settings.get('http GET transformer').uri;
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(dataFn === undefined);
});
});
});