hono 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,21 @@
1
- # hono
1
+ # Hono
2
2
 
3
- Minimal web framework for Cloudflare Workers.
3
+ Hono [炎] - Tiny web framework for Cloudflare Workers and others.
4
+
5
+ ```js
6
+ const app = Hono()
7
+
8
+ app.get('/', () => new Response('Hono!!'))
9
+
10
+ app.fire()
11
+ ```
12
+
13
+ ## Feature
14
+
15
+ - Fast - the router is implemented with Trie-Tree structure.
16
+ - Tiny - use only standard API.
17
+ - Portable - zero dependencies.
18
+ - Optimized for Cloudflare Workers.
4
19
 
5
20
  ## Install
6
21
 
@@ -14,18 +29,48 @@ or
14
29
  $ npm install hono
15
30
  ```
16
31
 
17
- ## Hello hono!
32
+ ## Routing
33
+
34
+ ### Basic
18
35
 
19
36
  ```js
20
- const Hono = require("hono");
21
- const app = Hono();
37
+ app.get('/', () => 'GET /')
38
+ app.post('/', () => 'POST /')
39
+ app.get('/wild/*/card', () => 'GET /wild/*/card')
40
+ ```
22
41
 
23
- app.get("/", () => new Response("Hono!!"));
42
+ ### Named Parameter
24
43
 
25
- app.fire(); // call `addEventListener`
44
+ ```js
45
+ app.get('/user/:name', (req) => {
46
+ const name = req.params('name')
47
+ ...
48
+ })
26
49
  ```
27
50
 
28
- Then, run `wrangler dev`.
51
+ ### Regexp
52
+
53
+ ```js
54
+ app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (req) => {
55
+ const date = req.params('date')
56
+ const title = req.params('title')
57
+ ...
58
+ ```
59
+
60
+ ### Chained Route
61
+
62
+ ```js
63
+ app
64
+ .route('/api/book')
65
+ .get(() => 'GET /api/book')
66
+ .post(() => 'POST /api/book')
67
+ .put(() => 'PUT /api/book')
68
+ ```
69
+
70
+ ## Related projects
71
+
72
+ - goblin <https://github.com/bmf-san/goblin>
73
+ - itty-router <https://github.com/kwhitley/itty-router>
29
74
 
30
75
  ## Author
31
76
 
@@ -0,0 +1,7 @@
1
+ const Hono = require('../../src/hono')
2
+ const app = Hono()
3
+
4
+ app.get('/', () => 'Hono!!')
5
+ app.get('/hello', () => 'This is /hello')
6
+
7
+ app.fire()
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "sandbox",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 2,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "sandbox",
9
+ "version": "1.0.0",
10
+ "license": "ISC"
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "sandbox",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "author": "Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)",
10
+ "license": "ISC"
11
+ }
@@ -0,0 +1,8 @@
1
+ name = "basic"
2
+ type = "webpack"
3
+ route = ''
4
+ zone_id = ''
5
+ usage_model = ''
6
+ compatibility_flags = []
7
+ workers_dev = true
8
+ compatibility_date = "2021-12-15"
package/hono.mini.js ADDED
@@ -0,0 +1 @@
1
+ const Router=require("./router");class Route{constructor(t,e){this.method=t;this.handler=e}}class App{constructor(){this.router=new Router}addRoute(t,e,n){this.router.add(e,new Route(t,n))}handle(t){const e=this.dispatch(t.request);return t.respondWith(e)}dispatch(t){const e=new URL(t.url);const n=e.pathname;const r=this.router.match(n);if(!r){return this.notFound()}const o=t.method.toLowerCase();const s=r[0];if(s.method==o){const i=s.handler;return i(t)}return this.notFound()}notFound(){return new Response("Not Found",{status:404,headers:{"content-type":"text/plain"}})}fire(){addEventListener("fetch",t=>{this.handle(t)})}}const proxyHandler={get:(e,n)=>(...t)=>{if(e.constructor.prototype.hasOwnProperty(n)){return e[n](t[0])}else{e.addRoute(n,t[0],t[1]);return}}};const app=new App;function Hono(){return new Proxy(app,proxyHandler)}module.exports=Hono;class Router{constructor(){this.node=new Node({label:"/"})}add(t,e){this.node.insert(t,e)}match(t){return this.node.search(t)}}class Node{constructor({label:t,stuff:e,children:n}={}){this.label=t||"";this.stuff=e||{};this.children=n||[]}insert(t,e){let n=this;if(t=="/"){n.label=t;n.stuff=e}const r=this.splitPath(t);for(const o of r){let t=n.children[o];if(t){n=t}else{n.children[o]=new Node({label:o,stuff:e,children:[]});n=n.children[o]}}}splitPath(t){const e=[];for(const n of t.split("/")){if(n){e.push(n)}}return e}getPattern(t){const e=t.match(/^\:.+?\{(.+)\}$/);if(e){return"("+e[1]+")"}return"(.+)"}getParamName(t){const e=t.match(/^\:([^\{\}]+)/);if(e){return e[1]}}noRoute(){return null}search(t){let e=this;const n={};for(const r of this.splitPath(t)){const o=e.children[r];if(o){e=o;continue}if(Object.keys(e.children).length==0){if(e.label!=r){return this.noRoute()}break}let t=false;for(const s in e.children){if(s=="*"){e=e.children[s];t=true;break}if(s.match(/^:/)){const i=this.getPattern(s);const c=r.match(new RegExp(i));if(c){const h=this.getParamName(s);n[h]=c[0];e=e.children[s];t=true;break}return this.noRoute()}}if(t==false){return this.noRoute()}}return[e.stuff,n]}}module.exports=Router;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Minimal web framework for Cloudflare Workers",
5
5
  "main": "src/hono.js",
6
6
  "scripts": {
@@ -16,5 +16,9 @@
16
16
  "devDependencies": {
17
17
  "jest": "^27.4.5",
18
18
  "node-fetch": "^2.6.6"
19
+ },
20
+ "dependencies": {
21
+ "global": "^4.4.0",
22
+ "wrangler": "^0.0.0-beta.6"
19
23
  }
20
24
  }
package/src/hono.js CHANGED
@@ -1,77 +1,103 @@
1
- const Router = require('./router')
1
+ const Node = require('./node')
2
2
 
3
- class Route {
4
- constructor(method, handler) {
5
- this.method = method;
6
- this.handler = handler;
3
+ class Router {
4
+ constructor() {
5
+ this.node = new Node()
6
+ this.tempPath = '/'
7
7
  }
8
- }
9
8
 
10
- class App {
11
- constructor() {
12
- this.router = new Router();
9
+ route(path) {
10
+ this.tempPath = path
11
+ return WrappedRouter(this)
13
12
  }
14
13
 
15
14
  addRoute(method, path, handler) {
16
- this.router.add(path, new Route(method, handler))
15
+ this.node.insert(method, path, handler)
16
+ return WrappedRouter(this)
17
+ }
18
+
19
+ matchRoute(method, path) {
20
+ method = method.toLowerCase()
21
+ const res = this.node.search(method, path)
22
+ return res
17
23
  }
18
24
 
19
25
  handle(event) {
20
- const response = this.dispatch(event.request)
26
+ const result = this.dispatch(event.request)
27
+ const response = this.filter(result)
21
28
  return event.respondWith(response)
22
29
  }
23
30
 
31
+ filter(result) {
32
+ if (result instanceof Response) {
33
+ return result
34
+ }
35
+ if (typeof result === 'object') {
36
+ return new Response(JSON.stringify(result), {
37
+ status: 200,
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ },
41
+ })
42
+ }
43
+ if (typeof result === 'string') {
44
+ return new Response(result, {
45
+ status: 200,
46
+ headers: {
47
+ 'Content-Type': 'text/plain',
48
+ },
49
+ })
50
+ }
51
+ return this.notFound()
52
+ }
53
+
24
54
  dispatch(request) {
25
55
  const url = new URL(request.url)
26
56
  const path = url.pathname
27
- const match = this.router.match(path)
57
+ const method = request.method
58
+ const res = this.matchRoute(method, path)
28
59
 
29
- if (!match) {
60
+ if (!res) {
30
61
  return this.notFound()
31
62
  }
32
63
 
33
- const method = request.method.toLowerCase()
34
- const route = match[0]
35
- if (route.method == method) {
36
- const handler = route.handler
37
- return handler(request)
38
- }
39
- return this.notFound()
64
+ const handler = res.handler
65
+ return handler(request)
40
66
  }
41
67
 
42
68
  notFound() {
43
69
  return new Response('Not Found', {
44
70
  status: 404,
45
71
  headers: {
46
- 'content-type': 'text/plain'
47
- }
72
+ 'content-type': 'text/plain',
73
+ },
48
74
  })
49
75
  }
50
76
 
51
77
  fire() {
52
- addEventListener("fetch", (event) => {
78
+ addEventListener('fetch', (event) => {
53
79
  this.handle(event)
54
80
  })
55
81
  }
56
82
  }
57
83
 
58
84
  const proxyHandler = {
59
- get: (target, prop) => (...args) => {
60
- if (target.constructor.prototype.hasOwnProperty(prop)) {
61
- return target[prop](args[0])
62
- } else {
63
- target.addRoute(prop, args[0], args[1])
64
- return
65
- }
66
- }
85
+ get:
86
+ (target, prop) =>
87
+ (...args) => {
88
+ if (target.constructor.prototype.hasOwnProperty(prop)) {
89
+ return target[prop](...args)
90
+ } else {
91
+ if (args.length === 1) {
92
+ return target.addRoute(prop, target.tempPath, ...args)
93
+ }
94
+ return target.addRoute(prop, ...args)
95
+ }
96
+ },
67
97
  }
68
98
 
69
- const app = new App()
70
-
71
- function Hono() {
72
- return new Proxy(
73
- app, proxyHandler
74
- )
99
+ const WrappedRouter = (router = new Router()) => {
100
+ return new Proxy(router, proxyHandler)
75
101
  }
76
102
 
77
- module.exports = Hono
103
+ module.exports = WrappedRouter
package/src/hono.test.js CHANGED
@@ -1,17 +1,17 @@
1
1
  const Hono = require('./hono')
2
2
  const fetch = require('node-fetch')
3
3
 
4
- const app = new Hono()
4
+ const app = Hono()
5
5
 
6
6
  describe('GET match', () => {
7
7
  app.get('/hello', () => {
8
8
  return new fetch.Response('hello', {
9
- status: 200
9
+ status: 200,
10
10
  })
11
11
  })
12
12
  app.notFound = () => {
13
13
  return new fetch.Response('not found', {
14
- status: 404
14
+ status: 404,
15
15
  })
16
16
  }
17
17
  it('GET /hello is ok', () => {
@@ -1,44 +1,44 @@
1
- // Ref: https://github.com/bmf-san/goblin
1
+ const methodNameOfAll = 'all'
2
2
 
3
- class Router {
4
- constructor() {
5
- this.node = new Node({ label: "/" })
6
- }
7
- add(path, stuff) {
8
- this.node.insert(path, stuff);
9
- }
10
- match(path) {
11
- return this.node.search(path);
3
+ class Result {
4
+ constructor({ handler, params } = {}) {
5
+ this.handler = handler
6
+ this.params = params || {}
12
7
  }
13
8
  }
14
9
 
15
10
  class Node {
16
- constructor({ label, stuff, children } = {}) {
17
- this.label = label || "";
18
- this.stuff = stuff || {};
19
- this.children = children || [];
11
+ constructor({ method, label, handler, children } = {}) {
12
+ this.label = label || ''
13
+ this.children = children || []
14
+ this.method = {}
15
+ if (method && handler) {
16
+ this.method[method] = handler
17
+ }
20
18
  }
21
19
 
22
- insert(path, stuff) {
20
+ insert(method, path, handler) {
23
21
  let curNode = this
24
- if (path == '/') {
25
- curNode.label = path
26
- curNode.stuff = stuff
27
- }
28
22
  const ps = this.splitPath(path)
29
23
  for (const p of ps) {
30
24
  let nextNode = curNode.children[p]
31
25
  if (nextNode) {
32
26
  curNode = nextNode
33
27
  } else {
34
- curNode.children[p] = new Node({ label: p, stuff: stuff, children: [] })
28
+ curNode.children[p] = new Node({
29
+ label: p,
30
+ handler: handler,
31
+ })
35
32
  curNode = curNode.children[p]
36
33
  }
37
34
  }
35
+ if (!curNode.method[method]) {
36
+ curNode.method[method] = handler
37
+ }
38
38
  }
39
39
 
40
40
  splitPath(path) {
41
- const ps = []
41
+ let ps = ['/']
42
42
  for (const p of path.split('/')) {
43
43
  if (p) {
44
44
  ps.push(p)
@@ -64,30 +64,29 @@ class Node {
64
64
  }
65
65
  }
66
66
 
67
- noRoute() {
68
- return null
69
- }
70
-
71
- search(path) {
72
-
67
+ search(method, path) {
73
68
  let curNode = this
74
69
  const params = {}
75
70
 
71
+ if (path === '/') {
72
+ const root = this.children['/']
73
+ if (!root.method[method]) {
74
+ return this.noRoute()
75
+ }
76
+ }
77
+
76
78
  for (const p of this.splitPath(path)) {
77
- const nextNode = curNode.children[p]
79
+ let nextNode = curNode.children[p]
80
+
78
81
  if (nextNode) {
79
82
  curNode = nextNode
80
83
  continue
81
84
  }
82
- if (Object.keys(curNode.children).length == 0) {
83
- if (curNode.label != p) {
84
- return this.noRoute()
85
- }
86
- break
87
- }
85
+
88
86
  let isParamMatch = false
89
87
  for (const key in curNode.children) {
90
- if (key == "*") { // Wildcard
88
+ if (key === '*') {
89
+ // Wildcard
91
90
  curNode = curNode.children[key]
92
91
  isParamMatch = true
93
92
  break
@@ -109,8 +108,20 @@ class Node {
109
108
  return this.noRoute()
110
109
  }
111
110
  }
112
- return [curNode.stuff, params]
111
+
112
+ let handler = curNode.method[methodNameOfAll] || curNode.method[method]
113
+
114
+ if (handler) {
115
+ const res = new Result({ handler: handler, params: params })
116
+ return res
117
+ } else {
118
+ return this.noRoute()
119
+ }
120
+ }
121
+
122
+ noRoute() {
123
+ return null
113
124
  }
114
125
  }
115
126
 
116
- module.exports = Router
127
+ module.exports = Node
@@ -0,0 +1,17 @@
1
+ const Node = require('./node')
2
+
3
+ describe('Basic Usage', () => {
4
+ const node = new Node()
5
+ node.insert('get', '/', 'get root')
6
+ it('get /', () => {
7
+ expect(node.search('get', '/')).not.toBeNull()
8
+ })
9
+ })
10
+
11
+ describe('Basic Usage', () => {
12
+ const node = new Node()
13
+ node.insert('get', '/hello', 'get hello')
14
+ it('get /', () => {
15
+ expect(node.search('get', '/')).toBeNull()
16
+ })
17
+ })
@@ -0,0 +1,109 @@
1
+ const Node = require('./node')
2
+ const node = new Node()
3
+
4
+ describe('Util Methods', () => {
5
+ it('node.splitPath', () => {
6
+ let ps = node.splitPath('/')
7
+ expect(ps[0]).toBe('/')
8
+ ps = node.splitPath('/hello')
9
+ expect(ps[0]).toBe('/')
10
+ expect(ps[1]).toBe('hello')
11
+ })
12
+ })
13
+
14
+ describe('Basic Usage', () => {
15
+ node.insert('get', '/hello', 'get hello')
16
+ node.insert('post', '/hello', 'post hello')
17
+ node.insert('get', '/hello/foo', 'get hello foo')
18
+
19
+ it('get, post /hello', () => {
20
+ expect(node.search('get', '/')).toBeNull()
21
+ expect(node.search('post', '/')).toBeNull()
22
+
23
+ expect(node.search('get', '/hello').handler).toBe('get hello')
24
+ expect(node.search('post', '/hello').handler).toBe('post hello')
25
+ expect(node.search('put', '/hello')).toBeNull()
26
+ })
27
+ it('get /nothing', () => {
28
+ expect(node.search('get', '/nothing')).toBeNull()
29
+ })
30
+ it('/hello/foo, /hello/bar', () => {
31
+ expect(node.search('get', '/hello/foo').handler).toBe('get hello foo')
32
+ expect(node.search('post', '/hello/foo')).toBeNull()
33
+ expect(node.search('get', '/hello/bar')).toBeNull()
34
+ })
35
+ it('/hello/foo/bar', () => {
36
+ expect(node.search('get', '/hello/foo/bar')).toBeNull()
37
+ })
38
+ })
39
+
40
+ describe('Name path', () => {
41
+ it('get /entry/123', () => {
42
+ node.insert('get', '/entry/:id', 'get entry')
43
+ let res = node.search('get', '/entry/123')
44
+ expect(res).not.toBeNull()
45
+ expect(res.handler).toBe('get entry')
46
+ expect(res.params).not.toBeNull()
47
+ expect(res.params['id']).toBe('123')
48
+ expect(res.params['id']).not.toBe('1234')
49
+ })
50
+
51
+ it('get /entry/456/comment', () => {
52
+ node.insert('get', '/entry/:id', 'get entry')
53
+ res = node.search('get', '/entry/456/comment')
54
+ expect(res).toBeNull()
55
+ })
56
+
57
+ it('get /entry/789/comment/123', () => {
58
+ node.insert('get', '/entry/:id/comment/:comment_id', 'get comment')
59
+ res = node.search('get', '/entry/789/comment/123')
60
+ expect(res).not.toBeNull()
61
+ expect(res.handler).toBe('get comment')
62
+ expect(res.params['id']).toBe('789')
63
+ expect(res.params['comment_id']).toBe('123')
64
+ })
65
+ })
66
+
67
+ describe('Wildcard', () => {
68
+ it('/wildcard-abc/xxxxxx/wildcard-efg', () => {
69
+ node.insert('get', '/wildcard-abc/*/wildcard-efg', 'wildcard')
70
+ res = node.search('get', '/wildcard-abc/xxxxxx/wildcard-efg')
71
+ expect(res).not.toBeNull()
72
+ expect(res.handler).toBe('wildcard')
73
+ })
74
+ })
75
+
76
+ describe('Regexp', () => {
77
+ node.insert(
78
+ 'get',
79
+ '/regex-abc/:id{[0-9]+}/comment/:comment_id{[a-z]+}',
80
+ 'regexp'
81
+ )
82
+ it('/regexp-abc/123/comment/abc', () => {
83
+ res = node.search('get', '/regex-abc/123/comment/abc')
84
+ expect(res).not.toBeNull()
85
+ expect(res.handler).toBe('regexp')
86
+ expect(res.params['id']).toBe('123')
87
+ expect(res.params['comment_id']).toBe('abc')
88
+ })
89
+ it('/regexp-abc/abc', () => {
90
+ res = node.search('get', '/regex-abc/abc')
91
+ expect(res).toBeNull()
92
+ })
93
+ it('/regexp-abc/123/comment/123', () => {
94
+ res = node.search('get', '/regex-abc/123/comment/123')
95
+ expect(res).toBeNull()
96
+ })
97
+ })
98
+
99
+ describe('All', () => {
100
+ node.insert('all', '/all-methods', 'all methods')
101
+ it('/all-methods', () => {
102
+ res = node.search('get', '/all-methods')
103
+ expect(res).not.toBeNull()
104
+ expect(res.handler).toBe('all methods')
105
+ res = node.search('put', '/all-methods')
106
+ expect(res).not.toBeNull()
107
+ expect(res.handler).toBe('all methods')
108
+ })
109
+ })
@@ -1,66 +1,76 @@
1
- const Router = require('./router')
1
+ const Router = require('./hono')
2
+ let router = Router()
2
3
 
3
- let router = new Router()
4
+ describe('Basic Usage', () => {
5
+ it('get, post hello', () => {
6
+ router.get('/hello', 'get hello')
7
+ router.post('/hello', 'post hello')
4
8
 
5
- describe('root match', () => {
6
- it('/ match', () => {
7
- router.add('/', 'root')
8
- let match = router.match('/')
9
- expect(match).not.toBeNull()
10
- expect(match[0]).toBe('root')
11
- match = router.match('/foo')
12
- expect(match).toBeNull()
13
- })
9
+ let res = router.matchRoute('GET', '/hello')
10
+ expect(res).not.toBeNull()
11
+ expect(res.handler).toBe('get hello')
14
12
 
15
- it('/hello match', () => {
16
- router.add('/hello', 'hello')
17
- match = router.match('/foo')
18
- expect(match).toBeNull()
19
- match = router.match('/hello')
20
- expect(match[0]).toBe('hello')
21
- })
22
- })
13
+ res = router.matchRoute('POST', '/hello')
14
+ expect(res).not.toBeNull()
15
+ expect(res.handler).toBe('post hello')
23
16
 
24
- describe('path match', () => {
25
- router.add('/entry/:id', 'entry-id')
26
- router.add('/entry/:id/:comment', 'entry-id-comment')
27
- router.add('/year/:year{[0-9]{4}}/:month{[0-9]{2}}', 'date-regex')
17
+ res = router.matchRoute('PUT', '/hello')
18
+ expect(res).toBeNull()
28
19
 
29
- it('entry id match', () => {
30
- const match = router.match('/entry/123')
31
- expect(match[0]).toBe('entry-id')
32
- expect(match[1]['id']).toBe('123')
20
+ res = router.matchRoute('GET', '/')
21
+ expect(res).toBeNull()
33
22
  })
23
+ })
34
24
 
35
- it('entry id and comment match', () => {
36
- const match = router.match('/entry/123/45678')
37
- expect(match[0]).toBe('entry-id-comment')
38
- expect(match[1]['id']).toBe('123')
39
- expect(match[1]['comment']).toBe('45678')
25
+ describe('Complex', () => {
26
+ it('Named Param', () => {
27
+ router.get('/entry/:id', 'get entry')
28
+ res = router.matchRoute('GET', '/entry/123')
29
+ expect(res).not.toBeNull()
30
+ expect(res.handler).toBe('get entry')
31
+ expect(res.params['id']).toBe('123')
40
32
  })
41
-
42
- it('date-regex', () => {
43
- const match = router.match('/year/2021/12')
44
- expect(match[0]).toBe('date-regex')
45
- expect(match[1]['year']).toBe('2021')
46
- expect(match[1]['month']).toBe('12')
33
+ it('Wildcard', () => {
34
+ router.get('/wild/*/card', 'get wildcard')
35
+ res = router.matchRoute('GET', '/wild/xxx/card')
36
+ expect(res).not.toBeNull()
37
+ expect(res.handler).toBe('get wildcard')
47
38
  })
48
-
49
- it('not match', () => {
50
- let match = router.match('/foo')
51
- expect(match).toBeNull()
52
- match = router.match('/year/abc')
53
- expect(match).toBeNull()
39
+ it('Regexp', () => {
40
+ router.get('/post/:date{[0-9]+}/:title{[a-z]+}', 'get post')
41
+ res = router.matchRoute('GET', '/post/20210101/hello')
42
+ expect(res).not.toBeNull()
43
+ expect(res.handler).toBe('get post')
44
+ expect(res.params['date']).toBe('20210101')
45
+ expect(res.params['title']).toBe('hello')
46
+ res = router.matchRoute('GET', '/post/onetwothree')
47
+ expect(res).toBeNull()
48
+ res = router.matchRoute('GET', '/post/123/123')
49
+ expect(res).toBeNull()
54
50
  })
55
51
  })
56
52
 
57
- describe('wildcard', () => {
58
- it('match', () => {
59
- router = new Router()
60
- router.add('/abc/*/def')
61
- let match = router.match('/abc/xxx/def')
62
- expect(match).not.toBeNull()
63
- match = router.match('/abc/xxx/abc')
64
- expect(match).toBeNull()
53
+ describe('Chained Route', () => {
54
+ it('Return rooter object', () => {
55
+ router = router.patch('/hello', 'patch hello')
56
+ expect(router).not.toBeNull()
57
+ router = router.delete('/hello', 'delete hello')
58
+ res = router.matchRoute('DELETE', '/hello')
59
+ expect(res).not.toBeNull()
60
+ expect(res.handler).toBe('delete hello')
61
+ })
62
+ it('Chain with route method', () => {
63
+ router.route('/api/book').get('get book').post('post book').put('put book')
64
+ res = router.matchRoute('GET', '/api/book')
65
+ expect(res).not.toBeNull()
66
+ expect(res.handler).toBe('get book')
67
+ res = router.matchRoute('POST', '/api/book')
68
+ expect(res).not.toBeNull()
69
+ expect(res.handler).toBe('post book')
70
+ res = router.matchRoute('PUT', '/api/book')
71
+ expect(res).not.toBeNull()
72
+ expect(res.handler).toBe('put book')
73
+ res = router.matchRoute('DELETE', '/api/book')
74
+ expect(res).toBeNull()
65
75
  })
66
76
  })