diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 0000000..c34aa79 --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@commitlint/config-conventional'] +}; diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6c8b36 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..93bbd9b --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +!.*.js diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 3a78e47..0000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,52 +0,0 @@ - - -node.js version: - -npm/yarn and version: - -`@koa/router` version: - -`koa` version: - -#### Code sample: - - - -```js - -``` - -#### Expected Behavior: - - - -#### Actual Behavior: - - - -#### Additional steps, HTTP request details, or to reproduce the behavior or a test case: - - -```js -``` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..af76fae --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI +on: + - push + - pull_request +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + node_version: + - 12 + - 14 + - 16 + - 18 + name: Node ${{ matrix.node_version }} on ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node_version }} + - name: Install dependencies + run: npm install + - name: Run tests + run: npm run test diff --git a/.gitignore b/.gitignore index da3cb55..8366ce0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,15 @@ -# OS # -################### .DS_Store +*.log .idea -Thumbs.db -tmp/ -temp/ - - -# Node.js # -################### node_modules +coverage +.nyc_output +locales/ package-lock.json yarn.lock -*.log - -# NYC # -################### -coverage +Thumbs.db +tmp/ +temp/ *.lcov -.nyc_output \ No newline at end of file +.env \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 0000000..e8511ea --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx --no-install commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..3f5290b --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx --no-install lint-staged && npm test diff --git a/.lintstagedrc.js b/.lintstagedrc.js new file mode 100644 index 0000000..4f44c1d --- /dev/null +++ b/.lintstagedrc.js @@ -0,0 +1,5 @@ +module.exports = { + '*.md': (filenames) => filenames.map((filename) => `remark ${filename} -qfo`), + 'package.json': 'fixpack', + '*.js': 'xo --fix' +}; diff --git a/.npmrc b/.npmrc index 9cf9495..43c97e7 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1 @@ -package-lock=false \ No newline at end of file +package-lock=false diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..97a92d4 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,5 @@ +module.exports = { + singleQuote: true, + bracketSpacing: true, + trailingComma: 'none' +}; diff --git a/.remarkrc.js b/.remarkrc.js new file mode 100644 index 0000000..487138c --- /dev/null +++ b/.remarkrc.js @@ -0,0 +1,3 @@ +module.exports = { + plugins: ['preset-github'] +}; diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ae0b35d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: node_js -node_js: - - 8 - - 10 - - 12 - - 'stable' -script: - - npm run coverage \ No newline at end of file diff --git a/.xo-config.js b/.xo-config.js new file mode 100644 index 0000000..9ac8037 --- /dev/null +++ b/.xo-config.js @@ -0,0 +1,36 @@ +module.exports = { + prettier: true, + space: true, + extends: ['xo-lass'], + overrides: [ + { + files: ['test/**/*.js'], + env: ['mocha'] + } + ], + rules: { + 'array-callback-return': 'warn', + 'func-names': 'warn', + 'import/order': 'warn', + 'n/no-deprecated-api': 'warn', + 'n/prefer-global/process': 'warn', + 'n/prefer-promises/fs': 'warn', + 'new-cap': 'warn', + 'no-bitwise': 'warn', + 'no-implicit-coercion': 'warn', + 'no-inner-declarations': 'warn', + 'no-multi-assign': 'warn', + 'no-redeclare': 'warn', + 'no-return-assign': 'warn', + 'no-unused-vars': 'warn', + 'no-use-extend-native/no-use-extend-native': 'warn', + 'no-useless-call': 'warn', + 'no-var': 'warn', + 'prefer-const': 'warn', + 'prefer-rest-params': 'warn', + 'prefer-spread': 'warn', + 'unicorn/explicit-length-check': 'warn', + 'unicorn/no-array-reduce': 'warn', + 'unicorn/prefer-spread': 'warn' + } +}; diff --git a/API.md b/API.md index 2242e4e..6f48c24 100644 --- a/API.md +++ b/API.md @@ -1,42 +1,42 @@ +# API Reference -## API Reference - -- [API Reference](#api-reference) - - [Router ⏏](#router-) - - [new Router([opts])](#new-routeropts) - - [router.get|put|post|patch|delete|del ⇒ Router](#routergetputpostpatchdeletedel--router) - - [Named routes](#named-routes) - - [Multiple middleware](#multiple-middleware) - - [Nested routers](#nested-routers) - - [Router prefixes](#router-prefixes) - - [URL parameters](#url-parameters) - - [router.routes ⇒ function](#routerroutes--function) - - [router.use([path], middleware) ⇒ Router](#routerusepath-middleware--router) - - [router.prefix(prefix) ⇒ Router](#routerprefixprefix--router) - - [router.allowedMethods([options]) ⇒ function](#routerallowedmethodsoptions--function) - - [router.redirect(source, destination, [code]) ⇒ Router](#routerredirectsource-destination-code--router) - - [router.route(name) ⇒ Layer | false](#routerroutename--layer--false) - - [router.url(name, params, [options]) ⇒ String | Error](#routerurlname-params-options--string--error) - - [router.param(param, middleware) ⇒ Router](#routerparamparam-middleware--router) - - [Router.url(path, params) ⇒ String](#routerurlpath-params--string) - - - -### Router ⏏ -**Kind**: Exported class - - -#### new Router([opts]) -Create a new router. +## Table of Contents + +* [Router ⏏](#router-) + * [new Router(\[opts\])](#new-routeropts) + * [router.get|put|post|patch|delete|del ⇒ Router](#routergetputpostpatchdeletedel--coderoutercode) + * [Named routes](#named-routes) + * [Multiple middleware](#multiple-middleware) +* [Nested routers](#nested-routers) + * [Router prefixes](#router-prefixes) + * [URL parameters](#url-parameters) + * [router.routes ⇒ function](#routerroutes--codefunctioncode) + * [router.use(\[path\], middleware) ⇒ Router](#routerusepath-middleware--coderoutercode) + * [router.prefix(prefix) ⇒ Router](#routerprefixprefix--coderoutercode) + * [router.allowedMethods(\[options\]) ⇒ function](#routerallowedmethodsoptions--codefunctioncode) + * [router.redirect(source, destination, \[code\]) ⇒ Router](#routerredirectsource-destination-code--coderoutercode) + * [router.route(name) ⇒ Layer | false](#routerroutename--codelayercode--codefalsecode) + * [router.url(name, params, \[options\]) ⇒ String | Error](#routerurlname-params-options--codestringcode--codeerrorcode) + * [router.param(param, middleware) ⇒ Router](#routerparamparam-middleware--coderoutercode) + * [Router.url(path, params) ⇒ String](#routerurlpath-params--codestringcode) + + +## Router ⏏ + +**Kind**: Exported class + +### new Router(\[opts]) -| Param | Type | Description | -| --- | --- | --- | -| [opts] | Object | | -| [opts.prefix] | String | prefix router paths | +Create a new router. + +| Param | Type | Description | +| ---------------- | -------------------- | ------------------------------------------------------------------------ | +| [opts] | Object | | +| [opts.prefix] | String | prefix router paths | | [opts.exclusive] | Boolean | only run last matched route's controller when there are multiple matches | -**Example** +**Example** Basic usage: ```javascript @@ -54,9 +54,9 @@ app .use(router.routes()) .use(router.allowedMethods()); ``` - -#### router.get|put|post|patch|delete|del ⇒ Router +### router.get|put|post|patch|delete|del ⇒ Router + Create `router.verb()` methods, where *verb* is one of the HTTP verbs, such as `router.get()` or `router.post()`. @@ -92,7 +92,7 @@ Route paths will be translated to regular expressions using Query strings will not be considered when matching requests. -#### Named routes +### Named routes Routes can optionally have names. This allows generation of URLs and easy renaming of URLs during development. @@ -106,7 +106,7 @@ router.url('user', 3); // => "/users/3" ``` -#### Multiple middleware +### Multiple middleware Multiple middleware may be given: @@ -126,7 +126,8 @@ router.get( ); ``` -### Nested routers + +## Nested routers Nesting routers is supported: @@ -142,7 +143,7 @@ forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods()); app.use(forums.routes()); ``` -#### Router prefixes +### Router prefixes Route paths can be prefixed at the router level: @@ -155,7 +156,7 @@ router.get('/', ...); // responds to "/users" router.get('/:id', ...); // responds to "/users/:id" ``` -#### URL parameters +### URL parameters Named route parameters are captured and added to `ctx.params`. @@ -169,38 +170,38 @@ router.get('/:category/:title', (ctx, next) => { The [path-to-regexp](https://github.com/pillarjs/path-to-regexp) module is used to convert paths to regular expressions. -**Kind**: instance property of [Router](#exp_module_koa-router--Router) +**Kind**: instance property of [Router](#exp_module_koa-router--Router) -| Param | Type | Description | -| --- | --- | --- | -| path | String | | +| Param | Type | Description | +| ------------ | --------------------- | ------------------- | +| path | String | | | [middleware] | function | route middleware(s) | -| callback | function | route callback | +| callback | function | route callback | - +### router.routes ⇒ function -#### router.routes ⇒ function Returns router middleware which dispatches a route matching the request. -**Kind**: instance property of [Router](#exp_module_koa-router--Router) - +**Kind**: instance property of [Router](#exp_module_koa-router--Router) + +### router.use(\[path], middleware) ⇒ Router -#### router.use([path], middleware) ⇒ Router -Use given middleware, __if and only if__, a route is matched. +Use given middleware, **if and only if**, a route is matched. Middleware run in the order they are defined by `.use()`. They are invoked sequentially, requests start at the first middleware and work their way "down" the middleware stack. -**Kind**: instance method of [Router](#exp_module_koa-router--Router) +**Kind**: instance method of [Router](#exp_module_koa-router--Router) -| Param | Type | -| --- | --- | -| [path] | String | -| middleware | function | -| [...] | function | +| Param | Type | +| ---------- | --------------------- | +| [path] | String | +| middleware | function | +| [...] | function | + +**Example** -**Example** ```javascript // session middleware will run before authorize router @@ -215,18 +216,19 @@ router.use(['/users', '/admin'], userAuth()); app.use(router.routes()); ``` - -#### router.prefix(prefix) ⇒ Router +### router.prefix(prefix) ⇒ Router + Set the path prefix for a Router instance that was already initialized. -**Kind**: instance method of [Router](#exp_module_koa-router--Router) +**Kind**: instance method of [Router](#exp_module_koa-router--Router) + +| Param | Type | +| ------ | ------------------- | +| prefix | String | -| Param | Type | -| --- | --- | -| prefix | String | +**Example** -**Example** ```javascript const router = new Router({ prefix: '/categories' @@ -242,23 +244,23 @@ router.get('/:id', ...); // responds to "/users/:id" **Note**: prefix always should start from `/` otherwise it won't work. - +### router.allowedMethods(\[options]) ⇒ function -#### router.allowedMethods([options]) ⇒ function Returns separate middleware for responding to `OPTIONS` requests with an `Allow` header containing the allowed methods, as well as responding with `405 Method Not Allowed` and `501 Not Implemented` as appropriate. -**Kind**: instance method of [Router](#exp_module_koa-router--Router) +**Kind**: instance method of [Router](#exp_module_koa-router--Router) -| Param | Type | Description | -| --- | --- | --- | -| [options] | Object | | -| [options.throw] | Boolean | throw error instead of setting status and header | -| [options.notImplemented] | function | throw the returned value in place of the default NotImplemented error | +| Param | Type | Description | +| -------------------------- | --------------------- | ----------------------------------------------------------------------- | +| [options] | Object | | +| [options.throw] | Boolean | throw error instead of setting status and header | +| [options.notImplemented] | function | throw the returned value in place of the default NotImplemented error | | [options.methodNotAllowed] | function | throw the returned value in place of the default MethodNotAllowed error | -**Example** +**Example** + ```javascript const Koa = require('koa'); const Router = require('@koa/router'); @@ -287,9 +289,9 @@ app.use(router.allowedMethods({ methodNotAllowed: () => Boom.methodNotAllowed() })); ``` - -#### router.redirect(source, destination, [code]) ⇒ Router +### router.redirect(source, destination, \[code]) ⇒ Router + Redirect `source` to `destination` URL with optional 30x status `code`. Both `source` and `destination` can be route names. @@ -307,40 +309,39 @@ router.all('/login', ctx => { }); ``` -**Kind**: instance method of [Router](#exp_module_koa-router--Router) +**Kind**: instance method of [Router](#exp_module_koa-router--Router) -| Param | Type | Description | -| --- | --- | --- | -| source | String | URL or route name. | -| destination | String | URL or route name. | -| [code] | Number | HTTP status code (default: 301). | +| Param | Type | Description | +| ----------- | ------------------- | -------------------------------- | +| source | String | URL or route name. | +| destination | String | URL or route name. | +| [code] | Number | HTTP status code (default: 301). | - +### router.route(name) ⇒ Layer | false -#### router.route(name) ⇒ Layer | false Lookup route with given `name`. -**Kind**: instance method of [Router](#exp_module_koa-router--Router) +**Kind**: instance method of [Router](#exp_module_koa-router--Router) -| Param | Type | -| --- | --- | -| name | String | +| Param | Type | +| ----- | ------------------- | +| name | String | - +### router.url(name, params, \[options]) ⇒ String | Error -#### router.url(name, params, [options]) ⇒ String | Error Generate URL for route. Takes a route name and map of named `params`. -**Kind**: instance method of [Router](#exp_module_koa-router--Router) +**Kind**: instance method of [Router](#exp_module_koa-router--Router) -| Param | Type | Description | -| --- | --- | --- | -| name | String | route name | -| params | Object | url parameters | -| [options] | Object | options parameter | -| [options.query] | Object | String | query options | +| Param | Type | Description | +| --------------- | ------------------------------------------ | ----------------- | +| name | String | route name | +| params | Object | url parameters | +| [options] | Object | options parameter | +| [options.query] | Object \| String | query options | + +**Example** -**Example** ```javascript router.get('user', '/users/:id', (ctx, next) => { // ... @@ -363,20 +364,21 @@ router.url('user', { id: 3 }, { query: { limit: 1 } }); router.url('user', { id: 3 }, { query: "limit=1" }); // => "/users/3?limit=1" ``` - -#### router.param(param, middleware) ⇒ Router +### router.param(param, middleware) ⇒ Router + Run middleware for named route parameters. Useful for auto-loading or validation. -**Kind**: instance method of [Router](#exp_module_koa-router--Router) +**Kind**: instance method of [Router](#exp_module_koa-router--Router) + +| Param | Type | +| ---------- | --------------------- | +| param | String | +| middleware | function | -| Param | Type | -| --- | --- | -| param | String | -| middleware | function | +**Example** -**Example** ```javascript router .param('user', (id, ctx, next) => { @@ -395,19 +397,20 @@ router // /users/3 => {"id": 3, "name": "Alex"} // /users/3/friends => [{"id": 4, "name": "TJ"}] ``` - -#### Router.url(path, params) ⇒ String +### Router.url(path, params) ⇒ String + Generate URL from url pattern and given `params`. -**Kind**: static method of [Router](#exp_module_koa-router--Router) +**Kind**: static method of [Router](#exp_module_koa-router--Router) -| Param | Type | Description | -| --- | --- | --- | -| path | String | url pattern | +| Param | Type | Description | +| ------ | ------------------- | -------------- | +| path | String | url pattern | | params | Object | url parameters | -**Example** +**Example** + ```javascript const url = Router.url('/users/:id', {id: 1}); // => "/users/1" diff --git a/history.md b/HISTORY.md similarity index 52% rename from history.md rename to HISTORY.md index cb85e77..c3783eb 100644 --- a/history.md +++ b/HISTORY.md @@ -1,129 +1,158 @@ -9.0.0 / 2020-04-09 -================== +# History -- Update `path-to-regexp`. Migration path: change usage of `'*'` in routes to `(.*)` or `:splat*`. - - Example: `router.get('*', ....)` becomes `router.get('(.*)') ....)` +**[History has moved to the Releases tab of GitHub](https://github.com/koajs/router/releases).** -8.0.0 / 2019-06-16 -================== +## Log + + +## 9.0.0 / 2020-04-09 + +* Update `path-to-regexp`. Migration path: change usage of `'*'` in routes to `(.*)` or `:splat*`. + * Example: `router.get('*', ....)` becomes `router.get('(.*)') ....)` + + +## 8.0.0 / 2019-06-16 **others** - * [[`b5dd5e8`](http://github.com/koajs/koa-router/commit/b5dd5e8f00e841b7061a62ab6228cbe96a999470)] - chore: rename to @koa/router (dead-horse <>) -------------- +* [b5dd5e8](http://github.com/koajs/koa-router/commit/b5dd5e8f00e841b7061a62ab6228cbe96a999470)] - chore: rename to @koa/router (dead-horse) + +--- + +Changelogs inherit from koa-router. -# Changelogs inherit from koa-router. ## 7.4.0 -- Fix router.url() for multiple nested routers [#407](https://github.com/alexmingoia/koa-router/pull/407) -- `layer.name` added to `ctx` at `ctx.routerName` during routing [#412](https://github.com/alexmingoia/koa-router/pull/412) -- Router.use() was erroneously settings `(.*)` as a prefix to all routers nested with .use that did not pass an explicit prefix string as the first argument. This resulted in routes being matched that should not have been, included the running of multiple route handlers in error. [#369](https://github.com/alexmingoia/koa-router/issues/369) and [#410](https://github.com/alexmingoia/koa-router/issues/410) include information on this issue. +* Fix router.url() for multiple nested routers [#407](https://github.com/alexmingoia/koa-router/pull/407) +* `layer.name` added to `ctx` at `ctx.routerName` during routing [#412](https://github.com/alexmingoia/koa-router/pull/412) +* Router.use() was erroneously settings `(.*)` as a prefix to all routers nested with .use that did not pass an explicit prefix string as the first argument. This resulted in routes being matched that should not have been, included the running of multiple route handlers in error. [#369](https://github.com/alexmingoia/koa-router/issues/369) and [#410](https://github.com/alexmingoia/koa-router/issues/410) include information on this issue. + ## 7.3.0 -- Router#url() now accepts query parameters to add to generated urls [#396](https://github.com/alexmingoia/koa-router/pull/396) +* Router#url() now accepts query parameters to add to generated urls [#396](https://github.com/alexmingoia/koa-router/pull/396) + ## 7.2.1 -- Respond to CORS preflights with 200, 0 length body [#359](https://github.com/alexmingoia/koa-router/issues/359) +* Respond to CORS preflights with 200, 0 length body [#359](https://github.com/alexmingoia/koa-router/issues/359) + ## 7.2.0 -- Fix a bug in Router#url and append Router object to ctx. [#350](https://github.com/alexmingoia/koa-router/pull/350) -- Adds `_matchedRouteName` to context [#337](https://github.com/alexmingoia/koa-router/pull/337) -- Respond to CORS preflights with 200, 0 length body [#359](https://github.com/alexmingoia/koa-router/issues/359) +* Fix a bug in Router#url and append Router object to ctx. [#350](https://github.com/alexmingoia/koa-router/pull/350) +* Adds `_matchedRouteName` to context [#337](https://github.com/alexmingoia/koa-router/pull/337) +* Respond to CORS preflights with 200, 0 length body [#359](https://github.com/alexmingoia/koa-router/issues/359) + ## 7.1.1 -- Fix bug where param handlers were run out of order [#282](https://github.com/alexmingoia/koa-router/pull/282) +* Fix bug where param handlers were run out of order [#282](https://github.com/alexmingoia/koa-router/pull/282) + ## 7.1.0 -- Backports: merge 5.4 work into the 7.x upstream. See 5.4.0 updates for more details. +* Backports: merge 5.4 work into the 7.x upstream. See 5.4.0 updates for more details. + ## 7.0.1 -- Fix: allowedMethods should be ctx.method not this.method [#215](https://github.com/alexmingoia/koa-router/pull/215) +* Fix: allowedMethods should be ctx.method not this.method [#215](https://github.com/alexmingoia/koa-router/pull/215) + ## 7.0.0 -- The API has changed to match the new promise-based middleware +* The API has changed to match the new promise-based middleware signature of koa 2. See the [koa 2.x readme](https://github.com/koajs/koa/tree/2.0.0-alpha.3) for more information. -- Middleware is now always run in the order declared by `.use()` (or `.get()`, +* Middleware is now always run in the order declared by `.use()` (or `.get()`, etc.), which matches Express 4 API. + ## 5.4.0 -- Expose matched route at `ctx._matchedRoute`. +* Expose matched route at `ctx._matchedRoute`. + ## 5.3.0 -- Register multiple routes with array of paths [#203](https://github.com/alexmingoia/koa-router/issue/143). -- Improved router.url() [#143](https://github.com/alexmingoia/koa-router/pull/143) -- Adds support for named routes and regular expressions +* Register multiple routes with array of paths [#203](https://github.com/alexmingoia/koa-router/issue/143). +* Improved router.url() [#143](https://github.com/alexmingoia/koa-router/pull/143) +* Adds support for named routes and regular expressions [#152](https://github.com/alexmingoia/koa-router/pull/152) -- Add support for custom throw functions for 405 and 501 responses [#206](https://github.com/alexmingoia/koa-router/pull/206) +* Add support for custom throw functions for 405 and 501 responses [#206](https://github.com/alexmingoia/koa-router/pull/206) + ## 5.2.3 -- Fix for middleware running twice when nesting routes [#184](https://github.com/alexmingoia/koa-router/issues/184) +* Fix for middleware running twice when nesting routes [#184](https://github.com/alexmingoia/koa-router/issues/184) + ## 5.2.2 -- Register routes without params before those with params [#183](https://github.com/alexmingoia/koa-router/pull/183) -- Fix for allowed methods [#182](https://github.com/alexmingoia/koa-router/issues/182) +* Register routes without params before those with params [#183](https://github.com/alexmingoia/koa-router/pull/183) +* Fix for allowed methods [#182](https://github.com/alexmingoia/koa-router/issues/182) + ## 5.2.0 -- Add support for async/await. Resolves [#130](https://github.com/alexmingoia/koa-router/issues/130). -- Add support for array of paths by Router#use(). Resolves [#175](https://github.com/alexmingoia/koa-router/issues/175). -- Inherit param middleware when nesting routers. Fixes [#170](https://github.com/alexmingoia/koa-router/issues/170). -- Default router middleware without path to root. Fixes [#161](https://github.com/alexmingoia/koa-router/issues/161), [#155](https://github.com/alexmingoia/koa-router/issues/155), [#156](https://github.com/alexmingoia/koa-router/issues/156). -- Run nested router middleware after parent's. Fixes [#156](https://github.com/alexmingoia/koa-router/issues/156). -- Remove dependency on koa-compose. +* Add support for async/await. Resolves [#130](https://github.com/alexmingoia/koa-router/issues/130). +* Add support for array of paths by Router#use(). Resolves [#175](https://github.com/alexmingoia/koa-router/issues/175). +* Inherit param middleware when nesting routers. Fixes [#170](https://github.com/alexmingoia/koa-router/issues/170). +* Default router middleware without path to root. Fixes [#161](https://github.com/alexmingoia/koa-router/issues/161), [#155](https://github.com/alexmingoia/koa-router/issues/155), [#156](https://github.com/alexmingoia/koa-router/issues/156). +* Run nested router middleware after parent's. Fixes [#156](https://github.com/alexmingoia/koa-router/issues/156). +* Remove dependency on koa-compose. + ## 5.1.1 -- Match routes in order they were defined. Fixes #131. +* Match routes in order they were defined. Fixes #131. + ## 5.1.0 -- Support mounting router middleware at a given path. +* Support mounting router middleware at a given path. + ## 5.0.1 -- Fix bug with missing parameters when nesting routers. +* Fix bug with missing parameters when nesting routers. + ## 5.0.0 -- Remove confusing API for extending koa app with router methods. Router#use() +* Remove confusing API for extending koa app with router methods. Router#use() does not have the same behavior as app#use(). -- Add support for nesting routes. -- Remove support for regular expression routes to achieve nestable routers and +* Add support for nesting routes. +* Remove support for regular expression routes to achieve nestable routers and enable future trie-based routing optimizations. + ## 4.3.2 -- Do not send 405 if route matched but status is 404. Fixes #112, closes #114. +* Do not send 405 if route matched but status is 404. Fixes #112, closes #114. + ## 4.3.1 -- Do not run middleware if not yielded to by previous middleware. Fixes #115. +* Do not run middleware if not yielded to by previous middleware. Fixes #115. + ## 4.3.0 -- Add support for router prefixes. -- Add MIT license. +* Add support for router prefixes. +* Add MIT license. + ## 4.2.0 -- Fixed issue with router middleware being applied even if no route was -matched. -- Router.url - new static method to generate url from url pattern and data +* Fixed issue with router middleware being applied even if no route was + matched. +* Router.url - new static method to generate url from url pattern and data + ## 4.1.0 @@ -141,6 +170,7 @@ var matchedRoute = router.match(path).filter(function (route) { }).shift(); ``` + ## 4.0.0 405, 501, and OPTIONS response handling was moved into separate middleware @@ -151,7 +181,7 @@ using multiple routers. 4.x is mostly backwards compatible with 3.x, except for the following: -- Instantiating a router with `new` and `app` returns the router instance, +* Instantiating a router with `new` and `app` returns the router instance, whereas 3.x returns the router middleware. When creating a router in 4.x, the only time router middleware is returned is when creating using the `Router(app)` signature (with `app` and without `new`). diff --git a/LICENSE b/LICENSE index a0a52cf..ef66fed 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Alexander C. Mingoia -Copyright (c) 2019-present Nick Baugh and Koajs contributors +Copyright (c) 2015 Alexander C. Mingoia and @koajs contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. diff --git a/README.md b/README.md index 72e1ebf..6656824 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,26 @@ # [@koa/router](https://github.com/koajs/router) -> Router middleware for [Koa](https://github.com/koajs/koa). +> Router middleware for [Koa](https://github.com/koajs/koa). Maintained by [Forward Email][forward-email] and [Lad][]. -[![NPM version](https://img.shields.io/npm/v/@koa/router.svg?style=flat)](https://npmjs.org/package/@koa/router) -[![NPM Downloads](https://img.shields.io/npm/dm/@koa/router.svg?style=flat)](https://npmjs.org/package/@koa/router) -[![Node.js Version](https://img.shields.io/node/v/@koa/router.svg?style=flat)](http://nodejs.org/download/) -[![Build Status](https://img.shields.io/travis/koajs/router.svg?style=flat)](http://travis-ci.org/koajs/router) -[![gitter](https://img.shields.io/gitter/room/koajs/koa.svg?style=flat)](https://gitter.im/koajs/koa) +[![build status](https://github.com/koajs/router/actions/workflows/ci.yml/badge.svg)](https://github.com/koajs/router/actions/workflows/ci.yml) +[![code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) +[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) +[![made with lass](https://img.shields.io/badge/made_with-lass-95CC28.svg)](https://lass.js.org) +[![license](https://img.shields.io/github/license/koajs/router.svg)](LICENSE) + + +## Table of Contents + +* [Features](#features) +* [Migrating to 7 / Koa 2](#migrating-to-7--koa-2) +* [Install](#install) +* [Typescript Support](#typescript-support) +* [API Reference](#api-reference) +* [Contributors](#contributors) +* [License](#license) + + +## Features * Express-style routing (`app.get`, `app.put`, `app.post`, etc.) * Named URL parameters @@ -17,52 +31,54 @@ * Multiple and nestable routers * `async/await` support + ## Migrating to 7 / Koa 2 -- The API has changed to match the new promise-based middleware +* The API has changed to match the new promise-based middleware signature of koa 2. See the [koa 2.x readme](https://github.com/koajs/koa/tree/2.0.0-alpha.3) for more information. -- Middleware is now always run in the order declared by `.use()` (or `.get()`, +* Middleware is now always run in the order declared by `.use()` (or `.get()`, etc.), which matches Express 4 API. -## Installation -```bash -# npm .. -npm i @koa/router -# yarn .. -yarn add @koa/router +## Install + +[npm][]: + +```sh +npm install @koa/router ``` -## Typescript Support -To install types: +## Typescript Support -```bash -# npm .. -npm i @types/koa__router -# yarn .. -yarn add @types/koa__router +```sh +npm install @types/koa__router ``` + ## API Reference See [API Reference](./API.md) for more documentation. -## Contributing -Please submit all issues and pull requests to the [koajs/router](http://github.com/koajs/router) repository! +## Contributors + +| Name | +| ---------------- | +| **Alex Mingoia** | +| **@koajs** | + -## Support +## License -If you have any problem or suggestion please open an issue [here](https://github.com/koajs/router/issues). +[MIT](LICENSE) © Alex Mingoia -## Call for Maintainers -This module is forked from the original [koa-router](https://github.com/ZijianHe/koa-router) due to its lack of activity. `koa-router` is the most widely used router module in the Koa community and we need maintainers. If you're interested in fixing bugs or implementing new features feel free to open a pull request. We'll be adding active contributors as collaborators. +## -Thanks to the original authors @alexmingoia and the original team for their great work. +[forward-email]: https://forwardemail.net -### License +[lad]: https://lad.js.org -[MIT](LICENSE) +[npm]: https//www.npmjs.com diff --git a/bench/server.js b/bench/server.js index d3ac4e1..ae4b5af 100644 --- a/bench/server.js +++ b/bench/server.js @@ -1,13 +1,16 @@ const Koa = require('koa'); const Router = require('../'); -const env = require('@ladjs/env')({path:'../.env', includeProcessEnv: true, -assignToProcessEnv: true,}); +const env = require('@ladjs/env')({ + path: '../.env', + includeProcessEnv: true, + assignToProcessEnv: true +}); const app = new Koa(); const router = new Router(); -const ok = ctx => ctx.status = 200; -const n = parseInt(env.FACTOR || '10', 10); +const ok = (ctx) => (ctx.status = 200); +const n = Number.parseInt(env.FACTOR || '10', 10); const useMiddleware = env.USE_MIDDLEWARE === 'true'; router.get('/_health', ok); @@ -31,7 +34,7 @@ grandchild.get('/:id/seven', ok); grandchild.get('/:id/seven(/eight)?', ok); for (let i = n; i > 0; i--) { - let child = new Router(); + const child = new Router(); if (useMiddleware) child.use((ctx, next) => next()); child.get(`/:${''.padStart(i, 'a')}`, ok); child.middleware('/grandchild', grandchild); diff --git a/lib/layer.js b/lib/layer.js index 51265ab..5ad4d38 100644 --- a/lib/layer.js +++ b/lib/layer.js @@ -1,5 +1,5 @@ -const { pathToRegexp, compile, parse } = require('path-to-regexp'); const { parse: parseUrl, format: formatUrl } = require('url'); +const { pathToRegexp, compile, parse } = require('path-to-regexp'); module.exports = Layer; @@ -24,18 +24,20 @@ function Layer(path, methods, middleware, opts = {}) { this.paramNames = []; this.stack = Array.isArray(middleware) ? middleware : [middleware]; - for (let i = 0; i < methods.length; i++) { - const l = this.methods.push(methods[i].toUpperCase()); + for (const method of methods) { + const l = this.methods.push(method.toUpperCase()); if (this.methods[l - 1] === 'GET') this.methods.unshift('HEAD'); } // ensure middleware is a function for (let i = 0; i < this.stack.length; i++) { const fn = this.stack[i]; - const type = (typeof fn); + const type = typeof fn; if (type !== 'function') throw new Error( - `${methods.toString()} \`${this.opts.name || path}\`: \`middleware\` must be a function, not \`${type}\`` + `${methods.toString()} \`${ + this.opts.name || path + }\`: \`middleware\` must be a function, not \`${type}\`` ); } @@ -69,7 +71,8 @@ Layer.prototype.params = function (path, captures, params = {}) { for (let len = captures.length, i = 0; i < len; i++) { if (this.paramNames[i]) { const c = captures[i]; - if (c && c.length !== 0) params[this.paramNames[i].name] = c ? safeDecodeURIComponent(c) : c; + if (c && c.length > 0) + params[this.paramNames[i].name] = c ? safeDecodeURIComponent(c) : c; } } @@ -108,11 +111,11 @@ Layer.prototype.url = function (params, options) { let args = params; const url = this.path.replace(/\(\.\*\)/g, ''); - if (typeof params != 'object') { + if (typeof params !== 'object') { args = Array.prototype.slice.call(arguments); - if (typeof args[args.length - 1] == 'object') { + if (typeof args[args.length - 1] === 'object') { options = args[args.length - 1]; - args = args.slice(0, args.length - 1); + args = args.slice(0, -1); } } @@ -122,11 +125,11 @@ Layer.prototype.url = function (params, options) { const tokens = parse(url); let replace = {}; - if (args instanceof Array) { + if (Array.isArray(args)) { for (let len = tokens.length, i = 0, j = 0; i < len; i++) { if (tokens[i].name) replace[tokens[i].name] = args[j++]; } - } else if (tokens.some(token => token.name)) { + } else if (tokens.some((token) => token.name)) { replace = params; } else if (!options) { options = params; @@ -142,6 +145,7 @@ Layer.prototype.url = function (params, options) { replaced.search = undefined; replaced.query = options.query; } + return formatUrl(replaced); } @@ -172,11 +176,12 @@ Layer.prototype.url = function (params, options) { */ Layer.prototype.param = function (param, fn) { - const stack = this.stack; + const { stack } = this; const params = this.paramNames; const middleware = function (ctx, next) { return fn.call(this, ctx.params[param], ctx, next); }; + middleware.param = param; const names = params.map(function (p) { @@ -210,7 +215,10 @@ Layer.prototype.param = function (param, fn) { Layer.prototype.setPrefix = function (prefix) { if (this.path) { - this.path = (this.path !== '/' || this.opts.strict === true) ? `${prefix}${this.path}` : prefix + this.path = + this.path !== '/' || this.opts.strict === true + ? `${prefix}${this.path}` + : prefix; this.paramNames = []; this.regexp = pathToRegexp(this.path, this.paramNames, this.opts); } @@ -230,7 +238,7 @@ Layer.prototype.setPrefix = function (prefix) { function safeDecodeURIComponent(text) { try { return decodeURIComponent(text); - } catch (e) { + } catch { return text; } } diff --git a/lib/router.js b/lib/router.js index 8e2aaf3..d682c63 100644 --- a/lib/router.js +++ b/lib/router.js @@ -5,12 +5,15 @@ * @link https://github.com/alexmingoia/koa-router */ -const debug = require('debug')('koa-router'); +const { debuglog } = require('util'); + const compose = require('koa-compose'); const HttpError = require('http-errors'); const methods = require('methods'); -const Layer = require('./layer'); const { pathToRegexp } = require('path-to-regexp'); +const Layer = require('./layer'); + +const debug = debuglog('koa-router'); /** * @module koa-router @@ -61,7 +64,7 @@ function Router(opts = {}) { 'POST', 'DELETE' ]; - this.exclusive = !!this.opts.exclusive; + this.exclusive = Boolean(this.opts.exclusive); this.params = {}; this.stack = []; @@ -188,9 +191,9 @@ function Router(opts = {}) { * @returns {Router} */ -for (let i = 0; i < methods.length; i++) { +for (const method_ of methods) { function setMethodVerb(method) { - Router.prototype[method] = function(name, path, middleware) { + Router.prototype[method] = function (name, path, middleware) { if (typeof path === 'string' || path instanceof RegExp) { middleware = Array.prototype.slice.call(arguments, 2); } else { @@ -202,19 +205,24 @@ for (let i = 0; i < methods.length; i++) { // Sanity check to ensure we have a viable path candidate (eg: string|regex|non-empty array) if ( typeof path !== 'string' && - !(path instanceof RegExp) && + !(path instanceof RegExp) && (!Array.isArray(path) || path.length === 0) - ) throw new Error(`You have to provide a path when adding a ${method} handler`); + ) + throw new Error( + `You have to provide a path when adding a ${method} handler` + ); - this.register(path, [method], middleware, {name}); + this.register(path, [method], middleware, { name }); return this; }; } - setMethodVerb(methods[i]); + + setMethodVerb(method_); } // Alias for `router.delete()` because delete is a reserved word +// eslint-disable-next-line dot-notation Router.prototype.del = Router.prototype['delete']; /** @@ -254,9 +262,8 @@ Router.prototype.use = function () { // support array of paths if (Array.isArray(middleware[0]) && typeof middleware[0][0] === 'string') { - let arrPaths = middleware[0]; - for (let i = 0; i < arrPaths.length; i++) { - const p = arrPaths[i]; + const arrPaths = middleware[0]; + for (const p of arrPaths) { router.use.apply(router, [p].concat(middleware.slice(1))); } @@ -266,12 +273,15 @@ Router.prototype.use = function () { const hasPath = typeof middleware[0] === 'string'; if (hasPath) path = middleware.shift(); - for (let i = 0; i < middleware.length; i++) { - const m = middleware[i]; + for (const m of middleware) { if (m.router) { - const cloneRouter = Object.assign(Object.create(Router.prototype), m.router, { - stack: m.router.stack.slice(0) - }); + const cloneRouter = Object.assign( + Object.create(Router.prototype), + m.router, + { + stack: [...m.router.stack] + } + ); for (let j = 0; j < cloneRouter.stack.length; j++) { const nestedLayer = cloneRouter.stack[j]; @@ -289,18 +299,21 @@ Router.prototype.use = function () { if (router.params) { function setRouterParams(paramArr) { const routerParams = paramArr; - for (let j = 0; j < routerParams.length; j++) { - const key = routerParams[j]; + for (const key of routerParams) { cloneRouter.param(key, router.params[key]); } } + setRouterParams(Object.keys(router.params)); } } else { const keys = []; pathToRegexp(router.opts.prefix || '', keys); const routerPrefixHasParam = router.opts.prefix && keys.length; - router.register(path || '([^\/]*)', [], m, { end: false, ignoreCaptures: !hasPath && !routerPrefixHasParam }); + router.register(path || '([^/]*)', [], m, { + end: false, + ignoreCaptures: !hasPath && !routerPrefixHasParam + }); } } @@ -342,7 +355,7 @@ Router.prototype.prefix = function (prefix) { Router.prototype.routes = Router.prototype.middleware = function () { const router = this; - let dispatch = function dispatch(ctx, next) { + const dispatch = function dispatch(ctx, next) { debug('%s %s', ctx.method, ctx.path); const path = router.opts.routerPath || ctx.routerPath || ctx.path; @@ -359,23 +372,30 @@ Router.prototype.routes = Router.prototype.middleware = function () { if (!matched.route) return next(); - const matchedLayers = matched.pathAndMethod - const mostSpecificLayer = matchedLayers[matchedLayers.length - 1] + const matchedLayers = matched.pathAndMethod; + const mostSpecificLayer = matchedLayers[matchedLayers.length - 1]; ctx._matchedRoute = mostSpecificLayer.path; if (mostSpecificLayer.name) { ctx._matchedRouteName = mostSpecificLayer.name; } - layerChain = (router.exclusive ? [mostSpecificLayer] : matchedLayers).reduce(function(memo, layer) { - memo.push(function(ctx, next) { + layerChain = ( + router.exclusive ? [mostSpecificLayer] : matchedLayers + ).reduce(function (memo, layer) { + memo.push(function (ctx, next) { ctx.captures = layer.captures(path, ctx.captures); - ctx.params = ctx.request.params = layer.params(path, ctx.captures, ctx.params); + ctx.params = ctx.request.params = layer.params( + path, + ctx.captures, + ctx.params + ); ctx.routerPath = layer.path; ctx.routerName = layer.name; ctx._matchedRoute = layer.path; if (layer.name) { ctx._matchedRouteName = layer.name; } + return next(); }); return memo.concat(layer.stack); @@ -436,7 +456,7 @@ Router.prototype.allowedMethods = function (options = {}) { const implemented = this.methods; return function allowedMethods(ctx, next) { - return next().then(function() { + return next().then(function () { const allowed = {}; if (!ctx.status || ctx.status === 404) { @@ -452,25 +472,27 @@ Router.prototype.allowedMethods = function (options = {}) { if (!~implemented.indexOf(ctx.method)) { if (options.throw) { - let notImplementedThrowable = (typeof options.notImplemented === 'function') - ? options.notImplemented() // set whatever the user returns from their function - : new HttpError.NotImplemented(); + const notImplementedThrowable = + typeof options.notImplemented === 'function' + ? options.notImplemented() // set whatever the user returns from their function + : new HttpError.NotImplemented(); throw notImplementedThrowable; } else { ctx.status = 501; ctx.set('Allow', allowedArr.join(', ')); } - } else if (allowedArr.length) { + } else if (allowedArr.length > 0) { if (ctx.method === 'OPTIONS') { ctx.status = 200; ctx.body = ''; ctx.set('Allow', allowedArr.join(', ')); } else if (!allowed[ctx.method]) { if (options.throw) { - let notAllowedThrowable = (typeof options.methodNotAllowed === 'function') - ? options.methodNotAllowed() // set whatever the user returns from their function - : new HttpError.MethodNotAllowed(); + const notAllowedThrowable = + typeof options.methodNotAllowed === 'function' + ? options.methodNotAllowed() // set whatever the user returns from their function + : new HttpError.MethodNotAllowed(); throw notAllowedThrowable; } else { @@ -506,11 +528,12 @@ Router.prototype.all = function (name, path, middleware) { // Sanity check to ensure we have a viable path candidate (eg: string|regex|non-empty array) if ( typeof path !== 'string' && - !(path instanceof RegExp) && + !(path instanceof RegExp) && (!Array.isArray(path) || path.length === 0) - ) throw new Error('You have to provide a path when adding an all handler'); + ) + throw new Error('You have to provide a path when adding an all handler'); - this.register(path, methods, middleware, {name}); + this.register(path, methods, middleware, { name }); return this; }; @@ -549,13 +572,13 @@ Router.prototype.redirect = function (source, destination, code) { // lookup destination route by name if ( typeof destination === 'symbol' || - destination[0] !== '/' && !destination.includes('://') + (destination[0] !== '/' && !destination.includes('://')) ) { destination = this.url(destination); if (destination instanceof Error) throw destination; } - return this.all(source, ctx => { + return this.all(source, (ctx) => { ctx.redirect(destination); ctx.status = code || 301; }); @@ -573,12 +596,11 @@ Router.prototype.redirect = function (source, destination, code) { Router.prototype.register = function (path, methods, middleware, opts = {}) { const router = this; - const stack = this.stack; + const { stack } = this; // support array of paths if (Array.isArray(path)) { - for (let i = 0; i < path.length; i++) { - const curPath = path[i]; + for (const curPath of path) { router.register.call(router, curPath, methods, middleware, opts); } @@ -622,7 +644,7 @@ Router.prototype.register = function (path, methods, middleware, opts = {}) { Router.prototype.route = function (name) { const routes = this.stack; - for (let len = routes.length, i=0; i 0) matched.route = true; } } } @@ -742,7 +765,7 @@ Router.prototype.match = function (path, method) { * @returns {Router} */ -Router.prototype.param = function(param, middleware) { +Router.prototype.param = function (param, middleware) { this.params[param] = middleware; for (let i = 0; i < this.stack.length; i++) { const route = this.stack[i]; diff --git a/package.json b/package.json index 4a469ac..54665c1 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,44 @@ { "name": "@koa/router", - "description": "Router middleware for koa. Provides RESTful resource routing.", + "description": "Router middleware for koa. Maintained by Forward Email and Lad.", "version": "10.1.1", "author": "Alex Mingoia ", "bugs": { "url": "https://github.com/koajs/router/issues", "email": "niftylettuce@gmail.com" }, + "contributors": [ + "Alex Mingoia ", + "@koajs" + ], "dependencies": { - "debug": "^4.3.4", - "http-errors": "^1.7.3", + "http-errors": "^2.0.0", "koa-compose": "^4.1.0", "methods": "^1.1.2", "path-to-regexp": "^6.2.1" }, "devDependencies": { - "@ladjs/env": "^1.0.0", + "@commitlint/cli": "^17.0.3", + "@commitlint/config-conventional": "^17.0.3", + "@ladjs/env": "^3.0.0", + "ava": "^4.3.0", + "cross-env": "^7.0.3", + "eslint": "^8.19.0", + "eslint-config-xo-lass": "^2.0.1", "expect.js": "^0.3.1", - "jsdoc-to-markdown": "^5.0.3", + "fixpack": "^4.0.0", + "husky": "^8.0.1", + "jsdoc-to-markdown": "^7.1.1", "koa": "^2.13.4", + "lint-staged": "^13.0.3", "mocha": "^10.0.0", "nyc": "^15.1.0", + "remark-cli": "^11.0.0", + "remark-preset-github": "^4.0.4", "should": "^13.2.3", - "supertest": "^6.2.3", - "wrk": "^1.2.1" + "supertest": "^6.2.4", + "wrk": "^1.2.1", + "xo": "^0.50.0" }, "engines": { "node": ">= 12" @@ -45,10 +60,13 @@ "url": "https://github.com/koajs/router.git" }, "scripts": { + "bench": "make -C bench", + "coverage": "nyc npm run test", "docs": "NODE_ENV=test jsdoc2md -t ./lib/API_tpl.hbs --src ./lib/*.js >| API.md", + "lint": "xo --fix && remark . -qfo && fixpack", + "prepare": "husky install", + "pretest": "npm run lint", "test": "mocha test/**/*.js", - "test:watch": "mocha test/**/*.js --watch", - "coverage": "nyc npm run test", - "bench": "make -C bench" + "test:watch": "mocha test/**/*.js --watch" } } diff --git a/test/index.js b/test/index.js index 164089a..63cff94 100644 --- a/test/index.js +++ b/test/index.js @@ -4,8 +4,8 @@ const should = require('should'); -describe('module', function() { - it('should expose Router', function(done) { +describe('module', function () { + it('should expose Router', function (done) { const Router = require('..'); should.exist(Router); Router.should.be.type('function'); diff --git a/test/lib/layer.js b/test/lib/layer.js index 77ca8eb..f202be5 100644 --- a/test/lib/layer.js +++ b/test/lib/layer.js @@ -2,14 +2,14 @@ * Route tests */ -const Koa = require('koa'); const http = require('http'); +const Koa = require('koa'); const request = require('supertest'); const Router = require('../../lib/router'); const Layer = require('../../lib/layer'); -describe('Layer', function() { - it('composes multiple callbacks/middleware', function(done) { +describe('Layer', function () { + it('composes multiple callbacks/middleware', function (done) { const app = new Koa(); const router = new Router(); app.use(router.routes()); @@ -25,16 +25,16 @@ describe('Layer', function() { } ); request(http.createServer(app.callback())) - .get('/programming/how-to-node') - .expect(204) - .end(function(err) { - if (err) return done(err); - done(); - }); + .get('/programming/how-to-node') + .expect(204) + .end(function (err) { + if (err) return done(err); + done(); + }); }); - describe('Layer#match()', function() { - it('captures URL path parameters', function(done) { + describe('Layer#match()', function () { + it('captures URL path parameters', function (done) { const app = new Koa(); const router = new Router(); app.use(router.routes()); @@ -46,15 +46,15 @@ describe('Layer', function() { ctx.status = 204; }); request(http.createServer(app.callback())) - .get('/match/this') - .expect(204) - .end(function(err) { - if (err) return done(err); - done(); - }); + .get('/match/this') + .expect(204) + .end(function (err) { + if (err) return done(err); + done(); + }); }); - it('return original path parameters when decodeURIComponent throw error', function(done) { + it('return original path parameters when decodeURIComponent throw error', function (done) { const app = new Koa(); const router = new Router(); app.use(router.routes()); @@ -66,207 +66,237 @@ describe('Layer', function() { ctx.status = 204; }); request(http.createServer(app.callback())) - .get('/100%/101%') - .expect(204) - .end(done); + .get('/100%/101%') + .expect(204) + .end(done); }); - it('populates ctx.captures with regexp captures', function(done) { + it('populates ctx.captures with regexp captures', function (done) { const app = new Koa(); const router = new Router(); app.use(router.routes()); - router.get(/^\/api\/([^\/]+)\/?/i, function (ctx, next) { - ctx.should.have.property('captures'); - ctx.captures.should.be.instanceOf(Array); - ctx.captures.should.have.property(0, '1'); - return next(); - }, function (ctx) { - ctx.should.have.property('captures'); - ctx.captures.should.be.instanceOf(Array); - ctx.captures.should.have.property(0, '1'); - ctx.status = 204; - }); + router.get( + /^\/api\/([^/]+)\/?/i, + function (ctx, next) { + ctx.should.have.property('captures'); + ctx.captures.should.be.instanceOf(Array); + ctx.captures.should.have.property(0, '1'); + return next(); + }, + function (ctx) { + ctx.should.have.property('captures'); + ctx.captures.should.be.instanceOf(Array); + ctx.captures.should.have.property(0, '1'); + ctx.status = 204; + } + ); request(http.createServer(app.callback())) - .get('/api/1') - .expect(204) - .end(function(err) { - if (err) return done(err); - done(); - }); + .get('/api/1') + .expect(204) + .end(function (err) { + if (err) return done(err); + done(); + }); }); - it('return original ctx.captures when decodeURIComponent throw error', function(done) { + it('return original ctx.captures when decodeURIComponent throw error', function (done) { const app = new Koa(); const router = new Router(); app.use(router.routes()); - router.get(/^\/api\/([^\/]+)\/?/i, function (ctx, next) { - ctx.should.have.property('captures'); - ctx.captures.should.be.type('object'); - ctx.captures.should.have.property(0, '101%'); - return next(); - }, function (ctx) { - ctx.should.have.property('captures'); - ctx.captures.should.be.type('object'); - ctx.captures.should.have.property(0, '101%'); - ctx.status = 204; - }); + router.get( + /^\/api\/([^/]+)\/?/i, + function (ctx, next) { + ctx.should.have.property('captures'); + ctx.captures.should.be.type('object'); + ctx.captures.should.have.property(0, '101%'); + return next(); + }, + function (ctx) { + ctx.should.have.property('captures'); + ctx.captures.should.be.type('object'); + ctx.captures.should.have.property(0, '101%'); + ctx.status = 204; + } + ); request(http.createServer(app.callback())) - .get('/api/101%') - .expect(204) - .end(function(err) { - if (err) return done(err); - done(); - }); + .get('/api/101%') + .expect(204) + .end(function (err) { + if (err) return done(err); + done(); + }); }); - it('populates ctx.captures with regexp captures include undefined', function(done) { + it('populates ctx.captures with regexp captures include undefined', function (done) { const app = new Koa(); const router = new Router(); app.use(router.routes()); - router.get(/^\/api(\/.+)?/i, function (ctx, next) { - ctx.should.have.property('captures'); - ctx.captures.should.be.type('object'); - ctx.captures.should.have.property(0, undefined); - return next(); - }, function (ctx) { - ctx.should.have.property('captures'); - ctx.captures.should.be.type('object'); - ctx.captures.should.have.property(0, undefined); - ctx.status = 204; - }); + router.get( + /^\/api(\/.+)?/i, + function (ctx, next) { + ctx.should.have.property('captures'); + ctx.captures.should.be.type('object'); + ctx.captures.should.have.property(0, undefined); + return next(); + }, + function (ctx) { + ctx.should.have.property('captures'); + ctx.captures.should.be.type('object'); + ctx.captures.should.have.property(0, undefined); + ctx.status = 204; + } + ); request(http.createServer(app.callback())) - .get('/api') - .expect(204) - .end(function(err) { - if (err) return done(err); - done(); - }); + .get('/api') + .expect(204) + .end(function (err) { + if (err) return done(err); + done(); + }); }); - it('should throw friendly error message when handle not exists', function() { + it('should throw friendly error message when handle not exists', function () { const app = new Koa(); const router = new Router(); app.use(router.routes()); const notexistHandle = undefined; (function () { router.get('/foo', notexistHandle); - }).should.throw('get `/foo`: `middleware` must be a function, not `undefined`'); + }.should.throw( + 'get `/foo`: `middleware` must be a function, not `undefined`' + )); (function () { router.get('foo router', '/foo', notexistHandle); - }).should.throw('get `foo router`: `middleware` must be a function, not `undefined`'); + }.should.throw( + 'get `foo router`: `middleware` must be a function, not `undefined`' + )); (function () { - router.post('/foo', function() {}, notexistHandle); - }).should.throw('post `/foo`: `middleware` must be a function, not `undefined`'); + router.post('/foo', function () {}, notexistHandle); + }.should.throw( + 'post `/foo`: `middleware` must be a function, not `undefined`' + )); }); }); - describe('Layer#param()', function() { - it('composes middleware for param fn', function(done) { + describe('Layer#param()', function () { + it('composes middleware for param fn', function (done) { const app = new Koa(); const router = new Router(); - const route = new Layer('/users/:user', ['GET'], [function (ctx) { - ctx.body = ctx.user; - }]); + const route = new Layer( + '/users/:user', + ['GET'], + [ + function (ctx) { + ctx.body = ctx.user; + } + ] + ); route.param('user', function (id, ctx, next) { ctx.user = { name: 'alex' }; - if (!id) return ctx.status = 404; + if (!id) return (ctx.status = 404); return next(); }); router.stack.push(route); app.use(router.middleware()); request(http.createServer(app.callback())) - .get('/users/3') - .expect(200) - .end(function(err, res) { - if (err) return done(err); - res.should.have.property('body'); - res.body.should.have.property('name', 'alex'); - done(); - }); + .get('/users/3') + .expect(200) + .end(function (err, res) { + if (err) return done(err); + res.should.have.property('body'); + res.body.should.have.property('name', 'alex'); + done(); + }); }); - it('ignores params which are not matched', function(done) { + it('ignores params which are not matched', function (done) { const app = new Koa(); const router = new Router(); - const route = new Layer('/users/:user', ['GET'], [function (ctx) { - ctx.body = ctx.user; - }]); + const route = new Layer( + '/users/:user', + ['GET'], + [ + function (ctx) { + ctx.body = ctx.user; + } + ] + ); route.param('user', function (id, ctx, next) { ctx.user = { name: 'alex' }; - if (!id) return ctx.status = 404; + if (!id) return (ctx.status = 404); return next(); }); route.param('title', function (id, ctx, next) { ctx.user = { name: 'mark' }; - if (!id) return ctx.status = 404; + if (!id) return (ctx.status = 404); return next(); }); router.stack.push(route); app.use(router.middleware()); request(http.createServer(app.callback())) - .get('/users/3') - .expect(200) - .end(function(err, res) { - if (err) return done(err); - res.should.have.property('body'); - res.body.should.have.property('name', 'alex'); - done(); - }); + .get('/users/3') + .expect(200) + .end(function (err, res) { + if (err) return done(err); + res.should.have.property('body'); + res.body.should.have.property('name', 'alex'); + done(); + }); }); }); describe('Layer#params()', function () { let route; - before(function() { - route = new Layer('/:category', ['GET'], [function() {}]); + before(function () { + route = new Layer('/:category', ['GET'], [function () {}]); }); - it('should return an empty object if params were not pass', function() { + it('should return an empty object if params were not pass', function () { const params = route.params('', []); params.should.deepEqual({}); }); - it('should return empty object if params is empty string', function() { + it('should return empty object if params is empty string', function () { const params = route.params('', ['']); params.should.deepEqual({}); }); - it('should return an object with escaped params', function() { - const params = route.params('', ['how%20to%20node']) + it('should return an object with escaped params', function () { + const params = route.params('', ['how%20to%20node']); params.should.deepEqual({ category: 'how to node' }); }); - it('should return an object with the same params if an error occurs', function() { - const params = route.params('', ['%E0%A4%A']) + it('should return an object with the same params if an error occurs', function () { + const params = route.params('', ['%E0%A4%A']); params.should.deepEqual({ category: '%E0%A4%A' }); }); - it('should return an object with data if params were pass', function() { + it('should return an object with data if params were pass', function () { const params = route.params('', ['programming']); params.should.deepEqual({ category: 'programming' }); }); - - it('should return empty object if params were not pass', function() { + it('should return empty object if params were not pass', function () { route.paramNames = []; const params = route.params('', ['programming']); params.should.deepEqual({}); }); - }); - describe('Layer#url()', function() { - it('generates route URL', function() { - const route = new Layer('/:category/:title', ['get'], [function () {}], {name: 'books'}); + describe('Layer#url()', function () { + it('generates route URL', function () { + const route = new Layer('/:category/:title', ['get'], [function () {}], { + name: 'books' + }); let url = route.url({ category: 'programming', title: 'how-to-node' }); url.should.equal('/programming/how-to-node'); url = route.url('programming', 'how-to-node'); @@ -274,26 +304,32 @@ describe('Layer', function() { }); it('setPrefix method checks Layer for path', function () { - const route = new Layer('/category', ['get'], [function () {}], {name: 'books'}); - route.path = '/hunter2' - const prefix = route.setPrefix('TEST') - prefix.path.should.equal('TEST/hunter2') + const route = new Layer('/category', ['get'], [function () {}], { + name: 'books' + }); + route.path = '/hunter2'; + const prefix = route.setPrefix('TEST'); + prefix.path.should.equal('TEST/hunter2'); }); }); describe('Layer#prefix', () => { it('setPrefix method passes check Layer for path', function () { - const route = new Layer('/category', ['get'], [function () {}], {name: 'books'}); - route.path = '/hunter2' - const prefix = route.setPrefix('/TEST') - prefix.path.should.equal('/TEST/hunter2') + const route = new Layer('/category', ['get'], [function () {}], { + name: 'books' + }); + route.path = '/hunter2'; + const prefix = route.setPrefix('/TEST'); + prefix.path.should.equal('/TEST/hunter2'); }); it('setPrefix method fails check Layer for path', function () { - const route = new Layer(false, ['get'], [function () {}], {name: 'books'}); - route.path = false - const prefix = route.setPrefix('/TEST') - prefix.path.should.equal(false) + const route = new Layer(false, ['get'], [function () {}], { + name: 'books' + }); + route.path = false; + const prefix = route.setPrefix('/TEST'); + prefix.path.should.equal(false); }); }); }); diff --git a/test/lib/router.js b/test/lib/router.js index 5a7dccc..e599de2 100644 --- a/test/lib/router.js +++ b/test/lib/router.js @@ -4,15 +4,15 @@ const fs = require('fs'); const http = require('http'); +const path = require('path'); +const assert = require('assert'); const Koa = require('koa'); const methods = require('methods'); -const path = require('path'); const request = require('supertest'); -const Router = require('../../lib/router'); -const Layer = require('../../lib/layer'); const expect = require('expect.js'); const should = require('should'); -const assert = require('assert'); +const Router = require('../../lib/router'); +const Layer = require('../../lib/layer'); describe('Router', function () { it('creates new router with koa app', function (done) { @@ -21,7 +21,7 @@ describe('Router', function () { done(); }); - it('should', function(done) { + it('should', function (done) { const router = new Router(); console.info(router.params); @@ -68,10 +68,14 @@ describe('Router', function () { return next(); }); - parentRouter.use('/parent-route', function (ctx, next) { - ctx.n = ctx.n ? (ctx.n + 1) : 1; - return next(); - }, nestedRouter.routes()); + parentRouter.use( + '/parent-route', + function (ctx, next) { + ctx.n = ctx.n ? ctx.n + 1 : 1; + return next(); + }, + nestedRouter.routes() + ); app.use(parentRouter.routes()); @@ -99,7 +103,7 @@ describe('Router', function () { .expect(200) .end(function (err, res) { if (err) return done(err); - expect(res.body.url).to.eql("/"); + expect(res.body.url).to.eql('/'); done(); }); }); @@ -108,23 +112,28 @@ describe('Router', function () { const app = new Koa(); const router = new Router(); - router.get('/double', function (ctx, next) { - return new Promise(function (resolve) { - setTimeout(function () { - ctx.body = { message: 'Hello' }; - resolve(next()); - }, 1); - }); - }, function (ctx, next) { - return new Promise(function (resolve) { - setTimeout(function () { - ctx.body.message += ' World'; - resolve(next()); - }, 1); - }); - }, function (ctx) { - ctx.body.message += '!'; - }); + router.get( + '/double', + function (ctx, next) { + return new Promise(function (resolve) { + setTimeout(function () { + ctx.body = { message: 'Hello' }; + resolve(next()); + }, 1); + }); + }, + function (ctx, next) { + return new Promise(function (resolve) { + setTimeout(function () { + ctx.body.message += ' World'; + resolve(next()); + }, 1); + }); + }, + function (ctx) { + ctx.body.message += '!'; + } + ); app.use(router.routes()); @@ -154,9 +163,13 @@ describe('Router', function () { return next(); }); - parentRouter.use('/parent-route', function (ctx, next) { - return next(); - }, nestedRouter.routes()); + parentRouter.use( + '/parent-route', + function (ctx, next) { + return next(); + }, + nestedRouter.routes() + ); app.use(parentRouter.routes()); app.should.be.ok(); @@ -223,7 +236,7 @@ describe('Router', function () { expect(res.body).to.have.property('foo', 'bar'); expect(res.body).to.not.have.property('bar'); done(); - }) + }); }); it('matches first to last', function (done) { @@ -248,7 +261,7 @@ describe('Router', function () { if (err) return done(err); expect(res.body).to.have.property('order', 1); done(); - }) + }); }); it('runs multiple controllers when there are multiple matches', function (done) { @@ -261,7 +274,7 @@ describe('Router', function () { next(); }) .get('users_all', '/users/all', function (ctx, next) { - ctx.body = Object.assign({}, ctx.body, { all: true }); + ctx.body = { ...ctx.body, all: true }; next(); }); @@ -273,10 +286,10 @@ describe('Router', function () { expect(res.body).to.have.property('single', true); expect(res.body).to.have.property('all', true); done(); - }) + }); }); - it('runs only the last match when the \'exclusive\' option is enabled', function (done) { + it("runs only the last match when the 'exclusive' option is enabled", function (done) { const app = new Koa(); const router = new Router({ exclusive: true }); @@ -286,7 +299,7 @@ describe('Router', function () { next(); }) .get('users_all', '/users/all', function (ctx, next) { - ctx.body = Object.assign({}, ctx.body, { all: true }); + ctx.body = { ...ctx.body, all: true }; next(); }); @@ -298,24 +311,28 @@ describe('Router', function () { expect(res.body).to.not.have.property('single'); expect(res.body).to.have.property('all', true); done(); - }) + }); }); it('does not run subsequent middleware without calling next', function (done) { const app = new Koa(); const router = new Router(); - router - .get('user_page', '/user/(.*).jsx', function () { + router.get( + 'user_page', + '/user/(.*).jsx', + function () { // no next() - }, function (ctx) { + }, + function (ctx) { ctx.body = { order: 1 }; - }); + } + ); request(http.createServer(app.use(router.routes()).callback())) .get('/user/account.jsx') .expect(404) - .end(done) + .end(done); }); it('nests routers with prefixes at root', function (done) { @@ -447,10 +464,9 @@ describe('Router', function () { it('runs parent middleware for subrouter routes', function (done) { const app = new Koa(); - const subrouter = Router() - .get('/sub', function (ctx) { - ctx.body = { msg: ctx.msg }; - }); + const subrouter = Router().get('/sub', function (ctx) { + ctx.body = { msg: ctx.msg }; + }); const router = Router() .use(function (ctx, next) { ctx.msg = 'router'; @@ -523,7 +539,7 @@ describe('Router', function () { router.get('/resources/:id{.:ext}?', function (ctx) { ctx.should.have.property('params'); ctx.params.should.have.property('id', id); - if (ctx.params.ext) ctx.params.ext.should.be.equal(ext.substring(1)); + if (ctx.params.ext) ctx.params.ext.should.be.equal(ext.slice(1)); ctx.status = 204; }); const server = http.createServer(app.callback()); @@ -555,18 +571,22 @@ describe('Router', function () { ctx.bar = 'baz'; return next(); }); - router.get('/:category/:title', function (ctx, next) { - ctx.foo = 'bar'; - return next(); - }, function (ctx) { - ctx.should.have.property('bar', 'baz'); - ctx.should.have.property('foo', 'bar'); - ctx.should.have.property('app'); - ctx.should.have.property('req'); - ctx.should.have.property('res'); - ctx.status = 204; - done(); - }); + router.get( + '/:category/:title', + function (ctx, next) { + ctx.foo = 'bar'; + return next(); + }, + function (ctx) { + ctx.should.have.property('bar', 'baz'); + ctx.should.have.property('foo', 'bar'); + ctx.should.have.property('app'); + ctx.should.have.property('req'); + ctx.should.have.property('res'); + ctx.status = 204; + done(); + } + ); request(http.createServer(app.callback())) .get('/match/this') .expect(204) @@ -611,18 +631,19 @@ describe('Router', function () { }); }); }; - router - .get('/', function (ctx, next) { + + router.get( + '/', + function (ctx, next) { return next(); - }, function (ctx) { + }, + function (ctx) { return readVersion().then(function () { ctx.status = 204; }); - }); - request(http.createServer(app.callback())) - .get('/') - .expect(204) - .end(done); + } + ); + request(http.createServer(app.callback())).get('/').expect(204).end(done); }); describe('Router#allowedMethods()', function () { @@ -631,8 +652,8 @@ describe('Router', function () { const router = new Router(); app.use(router.routes()); app.use(router.allowedMethods()); - router.get('/users', function () { }); - router.put('/users', function () { }); + router.get('/users', function () {}); + router.put('/users', function () {}); request(http.createServer(app.callback())) .options('/users') .expect(200) @@ -647,9 +668,9 @@ describe('Router', function () { it('responds with 405 Method Not Allowed', function (done) { const app = new Koa(); const router = new Router(); - router.get('/users', function () { }); - router.put('/users', function () { }); - router.post('/events', function () { }); + router.get('/users', function () {}); + router.put('/users', function () {}); + router.post('/events', function () {}); app.use(router.routes()); app.use(router.allowedMethods()); request(http.createServer(app.callback())) @@ -678,9 +699,9 @@ describe('Router', function () { }); }); app.use(router.allowedMethods({ throw: true })); - router.get('/users', function () { }); - router.put('/users', function () { }); - router.post('/events', function () { }); + router.get('/users', function () {}); + router.put('/users', function () {}); + router.post('/events', function () {}); request(http.createServer(app.callback())) .post('/users') .expect(405) @@ -707,23 +728,25 @@ describe('Router', function () { ctx.status = err.statusCode; }); }); - app.use(router.allowedMethods({ - throw: true, - methodNotAllowed: function () { - const notAllowedErr = new Error('Custom Not Allowed Error'); - notAllowedErr.type = 'custom'; - notAllowedErr.statusCode = 405; - notAllowedErr.body = { - error: 'Custom Not Allowed Error', - statusCode: 405, - otherStuff: true - }; - return notAllowedErr; - } - })); - router.get('/users', function () { }); - router.put('/users', function () { }); - router.post('/events', function () { }); + app.use( + router.allowedMethods({ + throw: true, + methodNotAllowed() { + const notAllowedErr = new Error('Custom Not Allowed Error'); + notAllowedErr.type = 'custom'; + notAllowedErr.statusCode = 405; + notAllowedErr.body = { + error: 'Custom Not Allowed Error', + statusCode: 405, + otherStuff: true + }; + return notAllowedErr; + } + }) + ); + router.get('/users', function () {}); + router.put('/users', function () {}); + router.post('/events', function () {}); request(http.createServer(app.callback())) .post('/users') .expect(405) @@ -745,8 +768,8 @@ describe('Router', function () { const router = new Router(); app.use(router.routes()); app.use(router.allowedMethods()); - router.get('/users', function () { }); - router.put('/users', function () { }); + router.get('/users', function () {}); + router.put('/users', function () {}); request(http.createServer(app.callback())) .search('/users') .expect(501) @@ -772,8 +795,8 @@ describe('Router', function () { }); }); app.use(router.allowedMethods({ throw: true })); - router.get('/users', function () { }); - router.put('/users', function () { }); + router.get('/users', function () {}); + router.put('/users', function () {}); request(http.createServer(app.callback())) .search('/users') .expect(501) @@ -801,22 +824,24 @@ describe('Router', function () { ctx.status = err.statusCode; }); }); - app.use(router.allowedMethods({ - throw: true, - notImplemented: function () { - const notImplementedErr = new Error('Custom Not Implemented Error'); - notImplementedErr.type = 'custom'; - notImplementedErr.statusCode = 501; - notImplementedErr.body = { - error: 'Custom Not Implemented Error', - statusCode: 501, - otherStuff: true - }; - return notImplementedErr; - } - })); - router.get('/users', function () { }); - router.put('/users', function () { }); + app.use( + router.allowedMethods({ + throw: true, + notImplemented() { + const notImplementedErr = new Error('Custom Not Implemented Error'); + notImplementedErr.type = 'custom'; + notImplementedErr.statusCode = 501; + notImplementedErr.body = { + error: 'Custom Not Implemented Error', + statusCode: 501, + otherStuff: true + }; + return notImplementedErr; + } + }) + ); + router.get('/users', function () {}); + router.put('/users', function () {}); request(http.createServer(app.callback())) .search('/users') .expect(501) @@ -857,7 +882,7 @@ describe('Router', function () { app.use(router.routes()); app.use(router.allowedMethods()); - router.get('/', function () { }); + router.get('/', function () {}); request(http.createServer(app.callback())) .options('/') @@ -865,15 +890,16 @@ describe('Router', function () { .end(function (err, res) { if (err) return done(err); res.header.should.have.property('allow', 'HEAD, GET'); - let allowHeaders = res.res.rawHeaders.filter((item) => item === 'Allow'); + const allowHeaders = res.res.rawHeaders.filter( + (item) => item === 'Allow' + ); expect(allowHeaders.length).to.eql(1); done(); }); }); - }); - it("allowedMethods check if flow (allowedArr.length)", function (done) { + it('allowedMethods check if flow (allowedArr.length)', function (done) { const app = new Koa(); const router = new Router(); app.use(router.routes()); @@ -907,13 +933,13 @@ describe('Router', function () { .expect('GET /users', done); }); - it("parameter added to request in ctx", function (done) { + it('parameter added to request in ctx', function (done) { const app = new Koa(); const router = new Router(); - router.get("/echo/:saying", function (ctx) { + router.get('/echo/:saying', function (ctx) { try { - expect(ctx.params.saying).eql("helloWorld"); - expect(ctx.request.params.saying).eql("helloWorld"); + expect(ctx.params.saying).eql('helloWorld'); + expect(ctx.request.params.saying).eql('helloWorld'); ctx.body = { echo: ctx.params.saying }; } catch (err) { ctx.status = 500; @@ -922,16 +948,16 @@ describe('Router', function () { }); app.use(router.routes()); request(http.createServer(app.callback())) - .get("/echo/helloWorld") + .get('/echo/helloWorld') .expect(200) .end(function (err, res) { if (err) return done(err); - expect(res.body).to.eql({ echo: "helloWorld" }); + expect(res.body).to.eql({ echo: 'helloWorld' }); done(); }); }); - it("parameter added to request in ctx with sub router", function (done) { + it('parameter added to request in ctx with sub router', function (done) { const app = new Koa(); const router = new Router(); const subrouter = new Router(); @@ -941,17 +967,16 @@ describe('Router', function () { return next(); }); - subrouter - .get('/:saying', function (ctx) { - try { - expect(ctx.params.saying).eql("helloWorld"); - expect(ctx.request.params.saying).eql("helloWorld"); - ctx.body = { echo: ctx.params.saying }; - } catch (err) { - ctx.status = 500; - ctx.body = err.message; - } - }); + subrouter.get('/:saying', function (ctx) { + try { + expect(ctx.params.saying).eql('helloWorld'); + expect(ctx.request.params.saying).eql('helloWorld'); + ctx.body = { echo: ctx.params.saying }; + } catch (err) { + ctx.status = 500; + ctx.body = err.message; + } + }); router.use('/echo', subrouter.routes()); app.use(router.routes()); @@ -960,7 +985,7 @@ describe('Router', function () { .expect(200) .end(function (err, res) { if (err) return done(err); - expect(res.body).to.eql({ echo: "helloWorld" }); + expect(res.body).to.eql({ echo: 'helloWorld' }); done(); }); }); @@ -970,40 +995,41 @@ describe('Router', function () { const app = new Koa(); const router = new Router(); app.use(router.routes()); - methods.forEach(function (method) { + for (const method of methods) { router.should.have.property(method); router[method].should.be.type('function'); - router[method]('/', function () { }); - }); + router[method]('/', function () {}); + } + router.stack.should.have.length(methods.length); }); it('registers route with a regexp path', function () { const router = new Router(); - methods.forEach(function (method) { - router[method](/^\/\w$/i, function () { }).should.equal(router); - }); + for (const method of methods) { + router[method](/^\/\w$/i, function () {}).should.equal(router); + } }); it('registers route with a given name', function () { const router = new Router(); - methods.forEach(function (method) { - router[method](method, '/', function () { }).should.equal(router); - }); + for (const method of methods) { + router[method](method, '/', function () {}).should.equal(router); + } }); it('registers route with with a given name and regexp path', function () { const router = new Router(); - methods.forEach(function (method) { - router[method](method, /^\/$/i, function () { }).should.equal(router); - }); + for (const method of methods) { + router[method](method, /^\/$/i, function () {}).should.equal(router); + } }); it('enables route chaining', function () { const router = new Router(); - methods.forEach(function (method) { - router[method]('/', function () { }).should.equal(router); - }); + for (const method of methods) { + router[method]('/', function () {}).should.equal(router); + } }); it('registers array of paths (gh-203)', function () { @@ -1022,13 +1048,13 @@ describe('Router', function () { router.get('/notparameter', function (ctx) { ctx.body = { - param: ctx.params.parameter, + param: ctx.params.parameter }; }); router.get('/:parameter', function (ctx) { ctx.body = { - param: ctx.params.parameter, + param: ctx.params.parameter }; }); @@ -1043,24 +1069,28 @@ describe('Router', function () { done(); }); }); - - it ('correctly returns an error when not passed a path for verb-specific registration (gh-147)', function () { + + it('correctly returns an error when not passed a path for verb-specific registration (gh-147)', function () { const router = new Router(); - for (let el of methods) { - try { - router[el](function () {}); - } catch (e) { - expect(e.message).to.be(`You have to provide a path when adding a ${el} handler`); - } + for (const el of methods) { + try { + router[el](function () {}); + } catch (err) { + expect(err.message).to.be( + `You have to provide a path when adding a ${el} handler` + ); + } } }); - it ('correctly returns an error when not passed a path for "all" registration (gh-147)', function () { + it('correctly returns an error when not passed a path for "all" registration (gh-147)', function () { const router = new Router(); try { router.all(function () {}); - } catch (e) { - expect(e.message).to.be('You have to provide a path when adding an all handler'); + } catch (err) { + expect(err.message).to.be( + 'You have to provide a path when adding an all handler' + ); } }); }); @@ -1262,34 +1292,43 @@ describe('Router', function () { const nested = new Router({ prefix: '/qux' }); const pathList = ['/foo', '/bar']; - nested.get('/baz', ctx => { + nested.get('/baz', (ctx) => { ctx.body = { foo: ctx.foo, bar: ctx.bar, baz: 'baz' - } + }; }); - base.use(pathList, (ctx, next) => { - ctx.foo = 'foo'; - ctx.bar = 'bar'; + base.use( + pathList, + (ctx, next) => { + ctx.foo = 'foo'; + ctx.bar = 'bar'; - return next(); - }, nested.routes()); + return next(); + }, + nested.routes() + ); app.use(base.routes()); - Promise.all(pathList.map(pathname => { - return request(http.createServer(app.callback())) - .get(`/api${pathname}/qux/baz`) - .expect(200); - })).then((resList) => { - resList.forEach(res => { - assert.deepEqual(res.body, { foo: 'foo', bar: 'bar', baz: 'baz' }); - }); + Promise.all( + pathList.map((pathname) => { + return request(http.createServer(app.callback())) + .get(`/api${pathname}/qux/baz`) + .expect(200); + }) + ).then( + (resList) => { + for (const res of resList) { + assert.deepEqual(res.body, { foo: 'foo', bar: 'bar', baz: 'baz' }); + } - done(); - }, error => done(error)); + done(); + }, + (err) => done(err) + ); }); it('uses a same router middleware at given paths continuously - ZijianHe/koa-router#gh-244 gh-18', function (done) { @@ -1297,41 +1336,54 @@ describe('Router', function () { const base = new Router({ prefix: '/api' }); const nested = new Router({ prefix: '/qux' }); - nested.get('/baz', ctx => { + nested.get('/baz', (ctx) => { ctx.body = { foo: ctx.foo, bar: ctx.bar, baz: 'baz' - } + }; }); base - .use('/foo', (ctx, next) => { - ctx.foo = 'foo'; - ctx.bar = 'bar'; + .use( + '/foo', + (ctx, next) => { + ctx.foo = 'foo'; + ctx.bar = 'bar'; - return next(); - }, nested.routes()) - .use('/bar', (ctx, next) => { - ctx.foo = 'foo'; - ctx.bar = 'bar'; + return next(); + }, + nested.routes() + ) + .use( + '/bar', + (ctx, next) => { + ctx.foo = 'foo'; + ctx.bar = 'bar'; - return next(); - }, nested.routes()); + return next(); + }, + nested.routes() + ); app.use(base.routes()); - Promise.all(['/foo', '/bar'].map(pathname => { - return request(http.createServer(app.callback())) - .get(`/api${pathname}/qux/baz`) - .expect(200); - })).then((resList) => { - resList.forEach(res => { - assert.deepEqual(res.body, { foo: 'foo', bar: 'bar', baz: 'baz' }); - }); + Promise.all( + ['/foo', '/bar'].map((pathname) => { + return request(http.createServer(app.callback())) + .get(`/api${pathname}/qux/baz`) + .expect(200); + }) + ).then( + (resList) => { + for (const res of resList) { + assert.deepEqual(res.body, { foo: 'foo', bar: 'bar', baz: 'baz' }); + } - done(); - }, error => done(error)); + done(); + }, + (err) => done(err) + ); }); }); @@ -1341,7 +1393,7 @@ describe('Router', function () { const router = new Router(); router.should.have.property('register'); router.register.should.be.type('function'); - router.register('/', ['GET', 'POST'], function () { }); + router.register('/', ['GET', 'POST'], function () {}); app.use(router.routes()); router.stack.should.be.an.instanceOf(Array); router.stack.should.have.property('length', 1); @@ -1368,8 +1420,8 @@ describe('Router', function () { const app = new Koa(); const router = new Router(); app.use(router.routes()); - router.get('home', '/', function () { }); - router.get('sign-up-form', '/sign-up-form', function () { }); + router.get('home', '/', function () {}); + router.get('sign-up-form', '/sign-up-form', function () {}); router.redirect('home', 'sign-up-form'); request(http.createServer(app.callback())) .post('/') @@ -1387,8 +1439,8 @@ describe('Router', function () { app.use(router.routes()); const homeSymbol = Symbol('home'); const signUpFormSymbol = Symbol('sign-up-form'); - router.get(homeSymbol, '/', function () { }); - router.get(signUpFormSymbol, '/sign-up-form', function () { }); + router.get(homeSymbol, '/', function () {}); + router.get(signUpFormSymbol, '/sign-up-form', function () {}); router.redirect(homeSymbol, signUpFormSymbol); request(http.createServer(app.callback())) .post('/') @@ -1404,8 +1456,12 @@ describe('Router', function () { const router = new Router(); expect(() => router.redirect('missing', '/destination')).to.throwError(); expect(() => router.redirect('/source', 'missing')).to.throwError(); - expect(() => router.redirect(Symbol('missing'), '/destination')).to.throwError(); - expect(() => router.redirect('/source', Symbol('missing'))).to.throwError(); + expect(() => + router.redirect(Symbol('missing'), '/destination') + ).to.throwError(); + expect(() => + router.redirect('/source', Symbol('missing')) + ).to.throwError(); }); it('redirects to external sites', function (done) { @@ -1418,7 +1474,10 @@ describe('Router', function () { .expect(301) .end(function (err, res) { if (err) return done(err); - res.header.should.have.property('location', 'https://www.example.com'); + res.header.should.have.property( + 'location', + 'https://www.example.com' + ); done(); }); }); @@ -1433,7 +1492,10 @@ describe('Router', function () { .expect(301) .end(function (err, res) { if (err) return done(err); - res.header.should.have.property('location', 'my-custom-app-protocol://www.example.com/foo'); + res.header.should.have.property( + 'location', + 'my-custom-app-protocol://www.example.com/foo' + ); done(); }); }); @@ -1485,7 +1547,7 @@ describe('Router', function () { ); url.should.equal('/programming/how%20to%20node'); url = router.url('books', 'programming', 'how to node', { - encode: encodeURIComponent, + encode: encodeURIComponent }); url.should.equal('/programming/how%20to%20node'); done(); @@ -1494,15 +1556,19 @@ describe('Router', function () { it('generates URL for given route name within embedded routers', function (done) { const app = new Koa(); const router = new Router({ - prefix: "/books" + prefix: '/books' }); const embeddedRouter = new Router({ - prefix: "/chapters" - }); - embeddedRouter.get('chapters', '/:chapterName/:pageNumber', function (ctx) { - ctx.status = 204; + prefix: '/chapters' }); + embeddedRouter.get( + 'chapters', + '/:chapterName/:pageNumber', + function (ctx) { + ctx.status = 204; + } + ); router.use(embeddedRouter.routes()); app.use(router.routes()); let url = router.url( @@ -1512,7 +1578,7 @@ describe('Router', function () { ); url.should.equal('/books/chapters/Learning%20ECMA6/123'); url = router.url('chapters', 'Learning ECMA6', 123, { - encode: encodeURIComponent, + encode: encodeURIComponent }); url.should.equal('/books/chapters/Learning%20ECMA6/123'); done(); @@ -1521,13 +1587,13 @@ describe('Router', function () { it('generates URL for given route name within two embedded routers', function (done) { const app = new Koa(); const router = new Router({ - prefix: "/books" + prefix: '/books' }); const embeddedRouter = new Router({ - prefix: "/chapters" + prefix: '/chapters' }); const embeddedRouter2 = new Router({ - prefix: "/:chapterName/pages" + prefix: '/:chapterName/pages' }); embeddedRouter2.get('chapters', '/:pageNumber', function (ctx) { ctx.status = 204; @@ -1553,18 +1619,16 @@ describe('Router', function () { }); let url = router.url('books', 'programming', 4, { query }); url.should.equal('/books/programming/4?page=3&limit=10'); - url = router.url('books', - { category: 'programming', id: 4 }, - { query } - ); + url = router.url('books', { category: 'programming', id: 4 }, { query }); url.should.equal('/books/programming/4?page=3&limit=10'); - url = router.url('books', + url = router.url( + 'books', { category: 'programming', id: 4 }, { query: 'page=3&limit=10' } ); url.should.equal('/books/programming/4?page=3&limit=10'); done(); - }) + }); it('generates URL for given route name without params and query params', function (done) { const router = new Router(); @@ -1577,19 +1641,12 @@ describe('Router', function () { url.should.equal('/books', {}); var url = router.url('books'); url.should.equal('/books', {}, {}); - var url = router.url('books', - {}, - { query: { page: 3, limit: 10 } } - ); + var url = router.url('books', {}, { query: { page: 3, limit: 10 } }); url.should.equal('/books?page=3&limit=10'); - var url = router.url('books', - {}, - { query: 'page=3&limit=10' } - ); + var url = router.url('books', {}, { query: 'page=3&limit=10' }); url.should.equal('/books?page=3&limit=10'); done(); - }) - + }); it('generates URL for given route name without params and query params', function (done) { const router = new Router(); @@ -1601,7 +1658,7 @@ describe('Router', function () { }); url.should.equal('/category?page=3&limit=10'); done(); - }) + }); it('returns an Error if no route is found for name', function () { const app = new Koa(); @@ -1627,7 +1684,7 @@ describe('Router', function () { router .param('user', function (id, ctx, next) { ctx.user = { name: 'alex' }; - if (!id) return ctx.status = 404; + if (!id) return (ctx.status = 404); return next(); }) .get('/users/:user', function (ctx) { @@ -1653,7 +1710,8 @@ describe('Router', function () { if (ctx.ranFirst) { ctx.user.ordered = 'parameters'; } - if (!id) return ctx.status = 404; + + if (!id) return (ctx.status = 404); return next(); }) .param('first', function (id, ctx, next) { @@ -1661,17 +1719,15 @@ describe('Router', function () { if (ctx.user) { ctx.ranFirst = false; } - if (!id) return ctx.status = 404; + + if (!id) return (ctx.status = 404); return next(); }) .get('/:first/users/:user', function (ctx) { ctx.body = ctx.user; }); - request(http.createServer( - app - .use(router.routes()) - .callback())) + request(http.createServer(app.use(router.routes()).callback())) .get('/first/users/3') .expect(200) .end(function (err, res) { @@ -1708,10 +1764,7 @@ describe('Router', function () { ctx.body = ctx.state.loaded; }); - request(http.createServer( - app - .use(router.routes()) - .callback())) + request(http.createServer(app.use(router.routes()).callback())) .get('/1/2/3/4') .expect(200) .end(function (err, res) { @@ -1735,7 +1788,7 @@ describe('Router', function () { router .param('id', function (id, ctx, next) { ctx.params.id = 'ran'; - if (!id) return ctx.status = 404; + if (!id) return (ctx.status = 404); return next(); }) .use('/:id/children', subrouter.routes()); @@ -1762,10 +1815,7 @@ describe('Router', function () { router.get('/info', function (ctx) { ctx.body = 'hello'; }); - request(http.createServer( - app - .use(router.routes()) - .callback())) + request(http.createServer(app.use(router.routes()).callback())) .get('/info') .expect(200) .end(function (err, res) { @@ -1803,10 +1853,7 @@ describe('Router', function () { router.get('/info', function (ctx) { ctx.body = 'hello'; }); - request(http.createServer( - app - .use(router.routes()) - .callback())) + request(http.createServer(app.use(router.routes()).callback())) .get('/info/') .expect(404) .end(function (err) { @@ -1824,11 +1871,8 @@ describe('Router', function () { }); router.get('/info', function (ctx) { ctx.body = 'hello'; - }) - request(http.createServer( - app - .use(router.routes()) - .callback())) + }); + request(http.createServer(app.use(router.routes()).callback())) .get('/info') .expect(200) .end(function (err, res) { @@ -1845,11 +1889,8 @@ describe('Router', function () { }); router.get('/info', function (ctx) { ctx.body = 'hello'; - }) - request(http.createServer( - app - .use(router.routes()) - .callback())) + }); + request(http.createServer(app.use(router.routes()).callback())) .get('/info/') .expect(404) .end(function (err, res) { @@ -1868,6 +1909,7 @@ describe('Router', function () { middlewareCount++; return next(); }; + const middlewareB = function (ctx, next) { middlewareCount++; return next(); @@ -1883,10 +1925,7 @@ describe('Router', function () { expect(routerMiddleware).to.be.a('function'); - request(http.createServer( - app - .use(routerMiddleware) - .callback())) + request(http.createServer(app.use(routerMiddleware).callback())) .get('/users/1') .expect(200) .end(function (err, res) { @@ -1903,22 +1942,19 @@ describe('Router', function () { const router = new Router(); const middleware = function (ctx, next) { next(); - expect(ctx._matchedRoute).to.be('/users/:id') + expect(ctx._matchedRoute).to.be('/users/:id'); }; router.use(middleware); router.get('/users/:id', function (ctx) { - expect(ctx._matchedRoute).to.be('/users/:id') + expect(ctx._matchedRoute).to.be('/users/:id'); should.exist(ctx.params.id); ctx.body = { hello: 'world' }; }); const routerMiddleware = router.routes(); - request(http.createServer( - app - .use(routerMiddleware) - .callback())) + request(http.createServer(app.use(routerMiddleware).callback())) .get('/users/1') .expect(200) .end(function (err) { @@ -1932,8 +1968,8 @@ describe('Router', function () { const router = new Router(); router.get('users#show', '/users/:id', function (ctx) { - expect(ctx._matchedRouteName).to.be('users#show') - ctx.status = 200 + expect(ctx._matchedRouteName).to.be('users#show'); + ctx.status = 200; }); request(http.createServer(app.use(router.routes()).callback())) @@ -1950,8 +1986,8 @@ describe('Router', function () { const router = new Router(); router.get('/users/:id', function (ctx) { - expect(ctx._matchedRouteName).to.be(undefined) - ctx.status = 200 + expect(ctx._matchedRouteName).to.be(undefined); + ctx.status = 200; }); request(http.createServer(app.use(router.routes()).callback())) @@ -1968,8 +2004,8 @@ describe('Router', function () { const router = new Router(); router.get('/users/:id', function (ctx) { - expect(ctx.routerPath).to.be('/users/:id') - ctx.status = 200 + expect(ctx.routerPath).to.be('/users/:id'); + ctx.status = 200; }); request(http.createServer(app.use(router.routes()).callback())) @@ -1986,12 +2022,12 @@ describe('Router', function () { const router = new Router(); router.get('/users/list', function (ctx) { - expect(ctx._matchedRoute).to.be('/users/list') - ctx.status = 200 + expect(ctx._matchedRoute).to.be('/users/list'); + ctx.status = 200; }); router.get('/users/:id', function (ctx) { - expect(ctx._matchedRoute).to.be('/users/:id') - ctx.status = 200 + expect(ctx._matchedRoute).to.be('/users/:id'); + ctx.status = 200; }); request(http.createServer(app.use(router.routes()).callback())) @@ -2012,10 +2048,7 @@ describe('Router', function () { should.exist(ctx.params.id); ctx.body = 'hello'; }); - request(http.createServer( - app - .use(router.routes()) - .callback())) + request(http.createServer(app.use(router.routes()).callback())) .head('/users/1') .expect(200) .end(function (err, res) { @@ -2031,11 +2064,8 @@ describe('Router', function () { router.get('/users/:id', function (ctx) { should.exist(ctx.params.id); ctx.body = 'hello'; - }) - request(http.createServer( - app - .use(router.routes()) - .callback())) + }); + request(http.createServer(app.use(router.routes()).callback())) .head('/users/1') .expect(200) .end(function (err, res) { @@ -2058,7 +2088,7 @@ describe('Router', function () { const router = Router(); router.get('/users/:id', function (ctx) { ctx.body = 'test'; - }) + }); router.prefix('/things/:thing_id'); const route = router.stack[0]; expect(route.path).to.equal('/things/:thing_id/users/:id'); @@ -2067,7 +2097,6 @@ describe('Router', function () { expect(route.paramNames[1]).to.have.property('name', 'id'); }); - it('populates ctx.params correctly for router prefix (including use)', function (done) { const app = new Koa(); const router = new Router({ prefix: '/:category' }); @@ -2138,7 +2167,7 @@ describe('Router', function () { ctx.should.have.property('params'); ctx.params.should.be.type('object'); ctx.params.should.have.property('ping', 'pingKey'); - ctx.body = ctx.params + ctx.body = ctx.params; }); request(http.createServer(app.callback())) .get('/pingKey/pong') @@ -2277,7 +2306,7 @@ describe('Router', function () { done(); }); }); - } + }; } it(`prefix and '/' route behavior`, function (done) { @@ -2290,7 +2319,7 @@ describe('Router', function () { const strictRouter = new Router({ strict: true, prefix: '/bar' - }) + }); router.get('/', function (ctx) { ctx.body = ''; @@ -2333,12 +2362,15 @@ describe('Router', function () { }); }); }); - }) + }); }); describe('Static Router#url()', function () { it('generates route URL', function () { - const url = Router.url('/:category/:title', { category: 'programming', title: 'how-to-node' }); + const url = Router.url('/:category/:title', { + category: 'programming', + title: 'how-to-node' + }); url.should.equal('/programming/how-to-node'); }); @@ -2355,12 +2387,14 @@ describe('Router', function () { const query = { page: 3, limit: 10 }; let url = Router.url('/books/:category/:id', 'programming', 4, { query }); url.should.equal('/books/programming/4?page=3&limit=10'); - url = Router.url('/books/:category/:id', + url = Router.url( + '/books/:category/:id', { category: 'programming', id: 4 }, { query } ); url.should.equal('/books/programming/4?page=3&limit=10'); - url = Router.url('/books/:category/:id', + url = Router.url( + '/books/:category/:id', { category: 'programming', id: 4 }, { query: 'page=3&limit=10' } );