├── .github ├── CHANGELOG.md ├── CODEOWNERS.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── icon.jpg ├── icon.png └── workflows │ ├── publish-docs.yml │ ├── publish-egg.yml │ └── test.yml ├── .gitignore ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── Makefile ├── README.md ├── deps.ts ├── docs ├── _config.yaml ├── assets │ ├── css │ │ └── main.css │ ├── images │ │ ├── icons.png │ │ ├── icons@2x.png │ │ ├── widgets.png │ │ └── widgets@2x.png │ └── js │ │ ├── main.js │ │ └── search.json ├── globals.html ├── index.html └── modules │ └── _superoak_.html ├── egg.json ├── examples ├── README.md ├── expect-body │ ├── README.md │ ├── server.test.ts │ └── server.ts ├── nested-assertions │ ├── README.md │ ├── server.test.ts │ └── server.ts └── repeated-assertions │ ├── README.md │ ├── server.test.ts │ └── server.ts ├── lock.json ├── mod.ts ├── src └── superoak.ts ├── test ├── deps.ts ├── superoak.test.ts └── utils.ts └── version.ts /.github/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | For the latest changes, please refer to the [releases page](https://github.com/cmorten/superoak/releases). 4 | 5 | ## [4.7.0] - 12-03-2022 6 | 7 | - feat: Support Deno `1.19.3` and std `0.129.0` and other deps upgrades 8 | 9 | ## [4.6.0] - 05-01-2022 10 | 11 | - feat: Support Deno `1.17.1` and std `0.119.0` and other deps upgrades 12 | - docs: Add `nested-assertions` example 13 | - chore: Migrate from `asos-craigmorten` to `cmorten` 14 | 15 | ## [4.5.0] - 21-11-2021 16 | 17 | - feat: Support Deno `1.16.2` and std `0.115.1` and other deps upgrades 18 | - feat: Support `oak@v10.0.0` 19 | 20 | ## [4.4.0] - 13-07-2021 21 | 22 | - feat: Support Deno `1.12.0` and std `0.101.0` and other deps upgrades 23 | 24 | ## [4.3.0] - 25-06-2021 25 | 26 | - feat: Support Deno `1.11.2` and std `0.99.0` and other deps upgrades 27 | 28 | ## [4.2.0] - 26-04-2021 29 | 30 | - feat: Support Deno 1.9.2 and std 0.95.0 31 | - chore: upgrade dependencies 32 | 33 | ## [4.1.0] - 06-03-2021 34 | 35 | - feat: Support Deno 1.8.0 and std 0.89.0 36 | - chore: upgrade dependencies 37 | 38 | ## [4.0.0] - 10-02-2021 39 | 40 | - feat: upgrade to superdeno 4.0.0 (#19) 41 | - docs: simplify the contributing process 42 | 43 | ## [3.1.0] - 10-02-2021 44 | 45 | - fix: upgrade `superdeno` version to prevent need to supply superfluous 46 | `--location ` flag. 47 | 48 | ## [3.0.1] - 15-12-2020 49 | 50 | - feat: upgrade supported Deno to `1.6.1` and std module to `0.81.0`. 51 | - chore: remove lock fixing for ci build due to CDN hash issues. 52 | 53 | ## [3.0.0] - 12-12-2020 54 | 55 | - feat: upgrade to `superdeno@3.0.0` - **BREAKING CHANGE** support 56 | [superagent `.redirects(n)` API](https://visionmedia.github.io/superagent/#following-redirects), 57 | with a default of `0`. 58 | 59 | The consequence of upgrading superdeno to support the `.redirects(n)` API is 60 | that superoak follows a default of `0` redirects, for parity with 61 | [supertest](https://github.com/visionmedia/supertest/blob/master/lib/test.js#L32). 62 | If your test requires superoak to follow multiple redirects, specify the number 63 | of redirects required in `.redirects(n)`, or use `-1` to have superoak follow 64 | all redirects. 65 | 66 | ## [2.5.0] - 13-12-2020 67 | 68 | - feat: upgrade supported Deno to `1.6.0` and std module to `0.80.0`. 69 | - feat: upgrade superdeno to `2.5.0`. 70 | 71 | ## [2.4.1] - 07-12-2020 72 | 73 | - fix: upgrade superdeno to `2.4.1` for type fixes. 74 | 75 | ## [2.4.0] - 06-12-2020 76 | 77 | - feat: upgrade supported Deno to `1.5.4` and std module to `0.79.0`. 78 | - feat: upgrade superdeno to `2.4.0`. 79 | - fix: upgrade expect to `0.2.6` to fix import bug. 80 | - docs: update `contributing.md` r.e. getting started with an issue. 81 | 82 | ## [2.3.1] - 19-09-2020 83 | 84 | - chore: upgrade to eggs@0.2.2 in CI 85 | 86 | ## [2.3.0] - 19-09-2020 87 | 88 | - feat: upgrade supported Deno to `1.4.1` and std module to `0.70.0`. 89 | 90 | ## [2.2.0] - 24-08-2020 91 | 92 | - feat: upgrade supported Deno to `1.3.1` and std module to `0.66.0`. 93 | - feat: upgrade superdeno dep. 94 | 95 | ## [2.1.0] - 05-08-2020 96 | 97 | - chore: upgrade supported Deno and std module versions to `1.2.2` and `0.63.0`. 98 | - chore: fix modules to tagged versions as 99 | [commits and branches are no longer supported by Deno 100 | registry](https://deno.land/posts/registry2). 101 | - docs: remove reference to importing commit or branch from readme as not 102 | supported by Deno registry v2. 103 | 104 | ## [2.0.0] - 16-07-2020 105 | 106 | - feat: update to Deno `1.2.0` (breaking upgrade), std `0.61.0` and other dep 107 | upgrades. 108 | - chore: update formatting. 109 | 110 | ## [1.3.0] - 09-07-2020 111 | 112 | - feat: move to default branch of `main`. 113 | 114 | ## [1.2.0] - 09-07-2020 115 | 116 | - feat: support Deno `1.1.3`, std `0.60.0` and consume SuperDeno `1.6.1`. 117 | 118 | ## [1.1.1] - 23-06-2020 119 | 120 | - fix: examples using incorrect branch for Oak. 121 | 122 | ## [1.1.0] - 23-06-2020 123 | 124 | - chore: update dependencies. 125 | - feat: support registry. 126 | - docs: update README.md with SuperOak icon. 127 | 128 | ## [1.0.2] - 11-06-2020 129 | 130 | - docs: add examples matching the main repo README with tests. 131 | 132 | ## [1.0.1] - 02-06-2020 133 | 134 | - fix: decouple SuperOak from Oak v5.0.0 to prevent type errors. 135 | 136 | ## [1.0.0] - 01-06-2020 137 | 138 | - feat: initial Oak extension for SuperDeno - _SuperOak_. 139 | -------------------------------------------------------------------------------- /.github/CODEOWNERS.md: -------------------------------------------------------------------------------- 1 | * @cmorten 2 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this repository 2 | 3 | First of all, thanks for taking the time to read this document and contributing 4 | to our codebase! :tada: :beers: 5 | 6 | ## Getting started 7 | 8 | If you're working on an existing issue then awesome! Let us know by dropping a 9 | comment in the issue. 10 | 11 | If it's a new bug fix or feature that you would like to contribute, then please 12 | raise an issue so it can be tracked ( and to help out others who are 13 | experiencing the same issue / want the new thing know that it's being looked at! 14 | ). Be sure to check for existing issues before raising your own! 15 | 16 | ## Working on your feature 17 | 18 | ### Branching 19 | 20 | On this project we follow mainline development (or trunk based development), and 21 | our default branch is `main`. 22 | 23 | Therefore you need to branch from `main` and merge into `main`. 24 | 25 | ### Coding style 26 | 27 | Generally try to match the style and conventions of the code around your 28 | changes. Ultimately we want code that is clear, concise, consistent and easy to 29 | read. 30 | 31 | As this is a Deno project will also insist on meeting the Deno `fmt` standards. 32 | 33 | ### Format Code 34 | 35 | To format the code run: 36 | 37 | ```bash 38 | make fmt 39 | ``` 40 | 41 | ### Tests 42 | 43 | Before opening a PR, please run the following command to make sure your branch 44 | will build and pass all the checks and tests: 45 | 46 | ```console 47 | make ci 48 | ``` 49 | 50 | ## Opening a PR 51 | 52 | Once you're confident your branch is ready to review, open a PR against `main` 53 | on this repo. 54 | 55 | Please use the PR template as a guide, but if your change doesn't quite fit it, 56 | feel free to customize. 57 | 58 | ## Merging and publishing 59 | 60 | When your feature branch / PR has been tested and has an approval, it is then 61 | ready to merge. Please contact the maintainer to action the merge. 62 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [cmorten] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Issue 2 | 3 | Setup: 4 | 5 | - Deno Version: 6 | - v8 Version: 7 | - Typescript Version: 8 | - SuperOak Version: 9 | 10 | ## Details 11 | 12 | > Please replace this quote block with the details of the feature / bug you wish 13 | > to be addressed. If it is a bug please do your best to add steps to reproduce. 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Issue 2 | 3 | Fixes <#_ISSUE_ID_> 4 | 5 | ## Details 6 | 7 | Brief summary of PR purpose and code changes. 8 | 9 | ## CheckList 10 | 11 | - [ ] PR starts with [#_ISSUE_ID_]. 12 | - [ ] Has been tested (where required) before merge to main. 13 | -------------------------------------------------------------------------------- /.github/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmorten/superoak/8fa1820fda29b97021d60ea5756ccfa07616325c/.github/icon.jpg -------------------------------------------------------------------------------- /.github/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmorten/superoak/8fa1820fda29b97021d60ea5756ccfa07616325c/.github/icon.png -------------------------------------------------------------------------------- /.github/workflows/publish-docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish TypeDocs 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | publish-docs: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Use Node.js 20 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: 20 17 | - name: Install deps 18 | run: make deps 19 | - name: Use Deno 20 | uses: denolib/setup-deno@v2 21 | with: 22 | deno-version: 2.3.3 23 | - run: make typedoc 24 | - run: make ci 25 | - name: Publish Updated Type Docs 26 | uses: stefanzweifel/git-auto-commit-action@v4 27 | with: 28 | commit_message: publish typedocs 29 | push_options: --force 30 | -------------------------------------------------------------------------------- /.github/workflows/publish-egg.yml: -------------------------------------------------------------------------------- 1 | name: Publish Egg 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | publish-egg: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: denolib/setup-deno@v2 13 | with: 14 | deno-version: 2.3.3 15 | - run: deno install -A -f --unstable -n eggs https://x.nest.land/eggs@0.3.8/eggs.ts 16 | - run: | 17 | export PATH="/home/runner/.deno/bin:$PATH" 18 | eggs link ${NEST_LAND_KEY} 19 | eggs publish --yes 20 | env: 21 | NEST_LAND_KEY: ${{secrets.NEST_LAND_KEY}} 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macos-latest] 14 | deno-version: [2.3.3] 15 | 16 | runs-on: ${{ matrix.os }} 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Deno ${{ matrix.deno-version }} 21 | uses: denolib/setup-deno@v2 22 | with: 23 | deno-version: ${{ matrix.deno-version }} 24 | - run: make ci 25 | 26 | test-win: 27 | strategy: 28 | matrix: 29 | os: [windows-latest] 30 | deno-version: [2.3.3] 31 | 32 | runs-on: ${{ matrix.os }} 33 | 34 | steps: 35 | - uses: actions/checkout@v2 36 | - name: Use Deno ${{ matrix.deno-version }} 37 | uses: denolib/setup-deno@v2 38 | with: 39 | deno-version: ${{ matrix.deno-version }} 40 | - run: make build 41 | - run: make test 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.unstable": true, 4 | "deno.lint": true, 5 | "[typescript]": { 6 | "editor.defaultFormatter": "denoland.vscode-deno" 7 | }, 8 | "[typescriptreact]": { 9 | "editor.defaultFormatter": "denoland.vscode-deno" 10 | }, 11 | "editor.defaultFormatter": "denoland.vscode-deno", 12 | "deno.suggest.imports.hosts": { 13 | "https://deno.land": false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . All complaints will be reviewed and investigated 64 | promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | . 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement 123 | ladder](https://github.com/mozilla/diversity). 124 | 125 | [homepage]: https://www.contributor-covenant.org 126 | 127 | For answers to common questions about this code of conduct, see the FAQ at 128 | . Translations are available at 129 | . 130 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2022 Craig Morten 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build ci deps doc fmt fmt-check lint lock precommit test typedoc 2 | 3 | FILES_TO_FORMAT = ./src ./test ./deps.ts ./mod.ts ./version.ts ./lock.json 4 | 5 | build: 6 | @deno run --allow-net="deno.land" --allow-import --reload mod.ts 7 | 8 | ci: 9 | @make fmt-check 10 | @make lint 11 | @make build 12 | @make test 13 | 14 | deps: 15 | @npm install -g typescript@4 typedoc@0.19.2 16 | 17 | doc: 18 | @deno doc ./mod.ts 19 | 20 | fmt: 21 | @deno fmt ${FILES_TO_FORMAT} 22 | 23 | fmt-check: 24 | @deno fmt --check ${FILES_TO_FORMAT} 25 | 26 | lint: 27 | @deno lint ${FILES_TO_FORMAT} 28 | 29 | lock: 30 | @deno run --allow-net --lock=lock.json --allow-import --reload mod.ts 31 | 32 | precommit: 33 | @make lock 34 | @make typedoc 35 | @make fmt 36 | @make fmt 37 | 38 | test: 39 | @deno test --allow-net --allow-read --allow-env --allow-import 40 | 41 | typedoc: 42 | @rm -rf docs 43 | @typedoc --ignoreCompilerErrors --out ./docs --mode modules --includeDeclarations --excludeExternals --name superoak ./src 44 | @make fmt 45 | @make fmt 46 | @echo 'future: true\nencoding: "UTF-8"\ninclude:\n - "_*_.html"\n - "_*_.*.html"' > ./docs/_config.yaml 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Super Oak standing in the rain at night – stoically facing the dark battle that is software engineering 3 |

SuperOak

4 |

5 |

6 | HTTP assertions for Deno's Oak web framework made easy via SuperDeno. 7 |

8 |

9 | Current version 10 | Current test status 11 | SuperOak docs 12 | PRs are welcome 13 | SuperOak issues 14 | SuperOak stars 15 | SuperOak forks 16 | SuperOak license 17 | SuperOak is maintained 18 |

19 |

20 | SuperOak latest /x/ version 21 | Minimum supported Deno version 22 | SuperOak dependency count 23 | SuperOak dependency outdatedness 24 | SuperOak cached size 25 |

26 | 27 | --- 28 | 29 | ## Table of Contents 30 | 31 | - [Table of Contents](#table-of-contents) 32 | - [About](#about) 33 | - [Installation](#installation) 34 | - [Example](#example) 35 | - [Documentation](#documentation) 36 | - [API](#api) 37 | - [FAQs](#faqs) 38 | - [`Property 'get' does not exist on type 'Promise'` 39 | error](#property-get-does-not-exist-on-type-promisesuperdeno-error) 40 | - [`Request has been terminated` error](#request-has-been-terminated-error) 41 | - [Contributing](#contributing) 42 | - [License](#license) 43 | 44 | ## About 45 | 46 | This module aims to provide a high-level abstraction for testing HTTP in Deno's 47 | Oak web framework. This is a wrapper compatibility layer around 48 | [SuperDeno](https://github.com/cmorten/superdeno) to reduce some of the 49 | boilerplate needed to setup Oak integration + functional tests. 50 | 51 | ## Installation 52 | 53 | This is a [Deno](https://deno.land/) module available to import direct from this 54 | repo and via the [Deno Registry](https://deno.land/x). 55 | 56 | Before importing, [download and install Deno](https://deno.land/#installation). 57 | 58 | You can then import SuperOak straight into your project: 59 | 60 | ```ts 61 | import { superoak } from "https://deno.land/x/superoak/mod.ts"; 62 | ``` 63 | 64 | SuperOak is also available on [nest.land](https://nest.land/package/superoak), a 65 | package registry for Deno on the Blockchain. 66 | 67 | > Note: Some examples in this README are using the unversioned form of the import URL. In production you should always use the versioned import form such as `https://deno.land/x/superoak@5.0.0/mod.ts`. 68 | 69 | ## Example 70 | 71 | You may pass a url string (for an already running Oak server), or an Oak 72 | `Application` object to `superoak()` - when passing an Oak `Application`, 73 | SuperOak will automatically handle the creation of a server, binding to a free 74 | ephemeral port and closing of the server on a call to `.end()`. 75 | 76 | SuperOak works with any Deno test framework. Here's an example with Deno's 77 | built-in test framework. 78 | 79 | ```ts 80 | import { Application, Router } from "jsr:@oak/oak@^17.1.4"; 81 | import { superoak } from "https://deno.land/x/superoak@5.0.0/mod.ts"; 82 | 83 | const router = new Router(); 84 | router.get("/", (ctx) => { 85 | ctx.response.body = "Hello Deno!"; 86 | }); 87 | router.post("/user", (ctx) => { 88 | ctx.response.body = "Post!"; 89 | }); 90 | 91 | const app = new Application(); 92 | app.use(router.routes()); 93 | app.use(router.allowedMethods()); 94 | 95 | // Send simple GET request 96 | Deno.test("it should support the Oak framework", async () => { 97 | const request = await superoak(app); 98 | await request.get("/").expect("Hello Deno!"); 99 | }); 100 | 101 | // Custom requests can be built with the superagent API 102 | // https://visionmedia.github.io/superagent/#post--put-requests. 103 | Deno.test("it should allow post requests", async () => { 104 | const request = await superoak(app); 105 | await request 106 | .post("/user") 107 | .set("Content-Type", "application/json") 108 | .send('{"name":"superoak"}') 109 | .expect(200); 110 | }); 111 | ``` 112 | 113 | Save the above to a file `demo.test.ts` and test it using 114 | `deno test --allow-net demo.test.ts`. 115 | 116 | For further examples, see the 117 | [SuperOak examples](https://github.com/cmorten/superoak/blob/main/examples/README.md), 118 | [tests](https://github.com/cmorten/superoak/blob/main/test/superoak.test.ts) 119 | or the 120 | [SuperDeno examples](https://github.com/cmorten/superdeno#example) for 121 | inspiration. 122 | 123 | ## Documentation 124 | 125 | - [SuperOak Type Docs](https://cmorten.github.io/superoak/) 126 | - [SuperOak Deno Docs](https://doc.deno.land/https/deno.land/x/superoak/mod.ts) 127 | - [SuperOak Examples](https://github.com/cmorten/superoak/blob/main/examples/README.md) 128 | - [License](https://github.com/cmorten/superoak/blob/main/LICENSE.md) 129 | - [Changelog](https://github.com/cmorten/superoak/blob/main/.github/CHANGELOG.md) 130 | 131 | ## API 132 | 133 | Please refer to the 134 | [SuperDeno API](https://github.com/cmorten/superdeno#api) and 135 | [SuperAgent API](https://visionmedia.github.io/superagent/). 136 | 137 | ## FAQs 138 | 139 | ### `Property 'get' does not exist on type 'Promise'` error 140 | 141 | Unlike [SuperDeno](https://github.com/cmorten/superdeno), `superoak()` 142 | returns a promise which will need to be awaited before you can call any method 143 | such as `.get("/")`. 144 | 145 | ```ts 146 | // ✅ works 147 | Deno.test("it will allow you to make assertions if you await it", async () => { 148 | const request = await superoak(app); 149 | await request.get("/").expect(200).expect("Hello Deno!"); 150 | }); 151 | 152 | // ❌ won't work 153 | Deno.test("it will allow you to make assertions if you await it", async () => { 154 | const request = superoak(app); 155 | await request.get("/").expect(200).expect("Hello Deno!"); // Boom 💥 `Property 'get' does not exist on type 'Promise'` 156 | }); 157 | ``` 158 | 159 | ### `Request has been terminated` error 160 | 161 | Unlike [SuperDeno](https://github.com/cmorten/superdeno), you cannot 162 | re-use SuperOak instances. If you try you will encounter an error similar to 163 | below: 164 | 165 | ```console 166 | Error: Request has been terminated 167 | Possible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc. 168 | at Test.Request.crossDomainError 169 | at XMLHttpRequestSham.xhr.onreadystatechange 170 | ... 171 | ``` 172 | 173 | This is because SuperOak instances automatically close the underlying Oak server 174 | once the assertion chain has completed. 175 | 176 | Instead you should make all of your assertions on a single SuperOak instance, or 177 | create a new SuperOak instance for subsequent assertions like below: 178 | 179 | ```ts 180 | // ✅ works 181 | Deno.test( 182 | "it will allow you to make multiple assertions on one SuperOak instance", 183 | async () => { 184 | const request = await superoak(app); 185 | await request.get("/").expect(200).expect("Hello Deno!"); 186 | } 187 | ); 188 | 189 | // ✅ works 190 | Deno.test( 191 | "it will allow you to re-use the Application for another SuperOak instance", 192 | async () => { 193 | const request1 = await superoak(app); 194 | await request1.get("/").expect(200); 195 | 196 | const request2 = await superoak(app); 197 | await request2.get("/").expect("Hello Deno!"); 198 | } 199 | ); 200 | 201 | // ❌ won't work 202 | Deno.test( 203 | "it will throw an error if try to re-use a SuperOak instance", 204 | async () => { 205 | const request = await superoak(app); 206 | await request.get("/").expect(200); 207 | await request.get("/").expect("Hello Deno!"); // Boom 💥 `Error: Request has been terminated` 208 | } 209 | ); 210 | ``` 211 | 212 | ## Contributing 213 | 214 | [Contributing guide](https://github.com/cmorten/superoak/blob/main/.github/CONTRIBUTING.md) 215 | 216 | --- 217 | 218 | ## License 219 | 220 | SuperOak is licensed under the [MIT License](./LICENSE.md). 221 | 222 | Icon designed and created by 223 | [Hannah Morten](https://www.linkedin.com/in/hannah-morten-b1218017a/). 224 | -------------------------------------------------------------------------------- /deps.ts: -------------------------------------------------------------------------------- 1 | export { 2 | type IRequest, 3 | type IResponse, 4 | type SuperDeno, 5 | superdeno, 6 | Test, 7 | } from "https://deno.land/x/superdeno@5.0.1/mod.ts"; 8 | export { getFreePort } from "https://deno.land/x/free_port@v1.2.0/mod.ts"; 9 | -------------------------------------------------------------------------------- /docs/_config.yaml: -------------------------------------------------------------------------------- 1 | future: true 2 | encoding: "UTF-8" 3 | include: 4 | - "_*_.html" 5 | - "_*_.*.html" 6 | -------------------------------------------------------------------------------- /docs/assets/css/main.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v1.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-family:sans-serif}button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4,.tsd-index-panel h3{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:"courier new",monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:"";content:none}small{font-size:80%}sub{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;top:-0.5em}sub{bottom:-0.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure,form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button]{-webkit-appearance:button;cursor:pointer;*overflow:visible}input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}.hljs{display:inline-block;padding:.5em;background:#fff;color:#000}.hljs-comment,.hljs-annotation,.hljs-template_comment,.diff .hljs-header,.hljs-chunk,.apache .hljs-cbracket{color:green}.hljs-keyword,.hljs-id,.hljs-built_in,.css .smalltalk .hljs-class,.hljs-winutils,.bash .hljs-variable,.tex .hljs-command,.hljs-request,.hljs-status,.nginx .hljs-title{color:blue}.xml .hljs-tag{color:blue}.xml .hljs-tag .hljs-value{color:blue}.hljs-string,.hljs-title,.hljs-parent,.hljs-tag .hljs-value,.hljs-rules .hljs-value{color:#a31515}.ruby .hljs-symbol{color:#a31515}.ruby .hljs-symbol .hljs-string{color:#a31515}.hljs-template_tag,.django .hljs-variable,.hljs-addition,.hljs-flow,.hljs-stream,.apache .hljs-tag,.hljs-date,.tex .hljs-formula,.coffeescript .hljs-attribute{color:#a31515}.ruby .hljs-string,.hljs-decorator,.hljs-filter .hljs-argument,.hljs-localvars,.hljs-array,.hljs-attr_selector,.hljs-pseudo,.hljs-pi,.hljs-doctype,.hljs-deletion,.hljs-envvar,.hljs-shebang,.hljs-preprocessor,.hljs-pragma,.userType,.apache .hljs-sqbracket,.nginx .hljs-built_in,.tex .hljs-special,.hljs-prompt{color:#2b91af}.hljs-phpdoc,.hljs-javadoc,.hljs-xmlDocTag{color:gray}.vhdl .hljs-typename{font-weight:bold}.vhdl .hljs-string{color:#666}.vhdl .hljs-literal{color:#a31515}.vhdl .hljs-attribute{color:#00b0e8}.xml .hljs-attribute{color:red}ul.tsd-descriptions>li>:first-child,.tsd-panel>:first-child,.col>:first-child,.col-11>:first-child,.col-10>:first-child,.col-9>:first-child,.col-8>:first-child,.col-7>:first-child,.col-6>:first-child,.col-5>:first-child,.col-4>:first-child,.col-3>:first-child,.col-2>:first-child,.col-1>:first-child,ul.tsd-descriptions>li>:first-child>:first-child,.tsd-panel>:first-child>:first-child,.col>:first-child>:first-child,.col-11>:first-child>:first-child,.col-10>:first-child>:first-child,.col-9>:first-child>:first-child,.col-8>:first-child>:first-child,.col-7>:first-child>:first-child,.col-6>:first-child>:first-child,.col-5>:first-child>:first-child,.col-4>:first-child>:first-child,.col-3>:first-child>:first-child,.col-2>:first-child>:first-child,.col-1>:first-child>:first-child,ul.tsd-descriptions>li>:first-child>:first-child>:first-child,.tsd-panel>:first-child>:first-child>:first-child,.col>:first-child>:first-child>:first-child,.col-11>:first-child>:first-child>:first-child,.col-10>:first-child>:first-child>:first-child,.col-9>:first-child>:first-child>:first-child,.col-8>:first-child>:first-child>:first-child,.col-7>:first-child>:first-child>:first-child,.col-6>:first-child>:first-child>:first-child,.col-5>:first-child>:first-child>:first-child,.col-4>:first-child>:first-child>:first-child,.col-3>:first-child>:first-child>:first-child,.col-2>:first-child>:first-child>:first-child,.col-1>:first-child>:first-child>:first-child{margin-top:0}ul.tsd-descriptions>li>:last-child,.tsd-panel>:last-child,.col>:last-child,.col-11>:last-child,.col-10>:last-child,.col-9>:last-child,.col-8>:last-child,.col-7>:last-child,.col-6>:last-child,.col-5>:last-child,.col-4>:last-child,.col-3>:last-child,.col-2>:last-child,.col-1>:last-child,ul.tsd-descriptions>li>:last-child>:last-child,.tsd-panel>:last-child>:last-child,.col>:last-child>:last-child,.col-11>:last-child>:last-child,.col-10>:last-child>:last-child,.col-9>:last-child>:last-child,.col-8>:last-child>:last-child,.col-7>:last-child>:last-child,.col-6>:last-child>:last-child,.col-5>:last-child>:last-child,.col-4>:last-child>:last-child,.col-3>:last-child>:last-child,.col-2>:last-child>:last-child,.col-1>:last-child>:last-child,ul.tsd-descriptions>li>:last-child>:last-child>:last-child,.tsd-panel>:last-child>:last-child>:last-child,.col>:last-child>:last-child>:last-child,.col-11>:last-child>:last-child>:last-child,.col-10>:last-child>:last-child>:last-child,.col-9>:last-child>:last-child>:last-child,.col-8>:last-child>:last-child>:last-child,.col-7>:last-child>:last-child>:last-child,.col-6>:last-child>:last-child>:last-child,.col-5>:last-child>:last-child>:last-child,.col-4>:last-child>:last-child>:last-child,.col-3>:last-child>:last-child>:last-child,.col-2>:last-child>:last-child>:last-child,.col-1>:last-child>:last-child>:last-child{margin-bottom:0}.container{max-width:1200px;margin:0 auto;padding:0 40px}@media(max-width: 640px){.container{padding:0 20px}}.container-main{padding-bottom:200px}.row{display:flex;position:relative;margin:0 -10px}.row:after{visibility:hidden;display:block;content:"";clear:both;height:0}.col,.col-11,.col-10,.col-9,.col-8,.col-7,.col-6,.col-5,.col-4,.col-3,.col-2,.col-1{box-sizing:border-box;float:left;padding:0 10px}.col-1{width:8.3333333333%}.offset-1{margin-left:8.3333333333%}.col-2{width:16.6666666667%}.offset-2{margin-left:16.6666666667%}.col-3{width:25%}.offset-3{margin-left:25%}.col-4{width:33.3333333333%}.offset-4{margin-left:33.3333333333%}.col-5{width:41.6666666667%}.offset-5{margin-left:41.6666666667%}.col-6{width:50%}.offset-6{margin-left:50%}.col-7{width:58.3333333333%}.offset-7{margin-left:58.3333333333%}.col-8{width:66.6666666667%}.offset-8{margin-left:66.6666666667%}.col-9{width:75%}.offset-9{margin-left:75%}.col-10{width:83.3333333333%}.offset-10{margin-left:83.3333333333%}.col-11{width:91.6666666667%}.offset-11{margin-left:91.6666666667%}.tsd-kind-icon{display:block;position:relative;padding-left:20px;text-indent:-20px}.tsd-kind-icon:before{content:"";display:inline-block;vertical-align:middle;width:17px;height:17px;margin:0 3px 2px 0;background-image:url(../images/icons.png)}@media(-webkit-min-device-pixel-ratio: 1.5),(min-resolution: 144dpi){.tsd-kind-icon:before{background-image:url(../images/icons@2x.png);background-size:238px 204px}}.tsd-signature.tsd-kind-icon:before{background-position:0 -153px}.tsd-kind-object-literal>.tsd-kind-icon:before{background-position:0px -17px}.tsd-kind-object-literal.tsd-is-protected>.tsd-kind-icon:before{background-position:-17px -17px}.tsd-kind-object-literal.tsd-is-private>.tsd-kind-icon:before{background-position:-34px -17px}.tsd-kind-class>.tsd-kind-icon:before{background-position:0px -34px}.tsd-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-17px -34px}.tsd-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-34px -34px}.tsd-kind-class.tsd-has-type-parameter>.tsd-kind-icon:before{background-position:0px -51px}.tsd-kind-class.tsd-has-type-parameter.tsd-is-protected>.tsd-kind-icon:before{background-position:-17px -51px}.tsd-kind-class.tsd-has-type-parameter.tsd-is-private>.tsd-kind-icon:before{background-position:-34px -51px}.tsd-kind-interface>.tsd-kind-icon:before{background-position:0px -68px}.tsd-kind-interface.tsd-is-protected>.tsd-kind-icon:before{background-position:-17px -68px}.tsd-kind-interface.tsd-is-private>.tsd-kind-icon:before{background-position:-34px -68px}.tsd-kind-interface.tsd-has-type-parameter>.tsd-kind-icon:before{background-position:0px -85px}.tsd-kind-interface.tsd-has-type-parameter.tsd-is-protected>.tsd-kind-icon:before{background-position:-17px -85px}.tsd-kind-interface.tsd-has-type-parameter.tsd-is-private>.tsd-kind-icon:before{background-position:-34px -85px}.tsd-kind-namespace>.tsd-kind-icon:before{background-position:0px -102px}.tsd-kind-namespace.tsd-is-protected>.tsd-kind-icon:before{background-position:-17px -102px}.tsd-kind-namespace.tsd-is-private>.tsd-kind-icon:before{background-position:-34px -102px}.tsd-kind-module>.tsd-kind-icon:before{background-position:0px -102px}.tsd-kind-module.tsd-is-protected>.tsd-kind-icon:before{background-position:-17px -102px}.tsd-kind-module.tsd-is-private>.tsd-kind-icon:before{background-position:-34px -102px}.tsd-kind-enum>.tsd-kind-icon:before{background-position:0px -119px}.tsd-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-17px -119px}.tsd-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-34px -119px}.tsd-kind-enum-member>.tsd-kind-icon:before{background-position:0px -136px}.tsd-kind-enum-member.tsd-is-protected>.tsd-kind-icon:before{background-position:-17px -136px}.tsd-kind-enum-member.tsd-is-private>.tsd-kind-icon:before{background-position:-34px -136px}.tsd-kind-signature>.tsd-kind-icon:before{background-position:0px -153px}.tsd-kind-signature.tsd-is-protected>.tsd-kind-icon:before{background-position:-17px -153px}.tsd-kind-signature.tsd-is-private>.tsd-kind-icon:before{background-position:-34px -153px}.tsd-kind-type-alias>.tsd-kind-icon:before{background-position:0px -170px}.tsd-kind-type-alias.tsd-is-protected>.tsd-kind-icon:before{background-position:-17px -170px}.tsd-kind-type-alias.tsd-is-private>.tsd-kind-icon:before{background-position:-34px -170px}.tsd-kind-type-alias.tsd-has-type-parameter>.tsd-kind-icon:before{background-position:0px -187px}.tsd-kind-type-alias.tsd-has-type-parameter.tsd-is-protected>.tsd-kind-icon:before{background-position:-17px -187px}.tsd-kind-type-alias.tsd-has-type-parameter.tsd-is-private>.tsd-kind-icon:before{background-position:-34px -187px}.tsd-kind-variable>.tsd-kind-icon:before{background-position:-136px -0px}.tsd-kind-variable.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -0px}.tsd-kind-variable.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -0px}.tsd-kind-variable.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -0px}.tsd-kind-variable.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -0px}.tsd-kind-variable.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -0px}.tsd-kind-variable.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -0px}.tsd-kind-variable.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -0px}.tsd-kind-variable.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -0px}.tsd-kind-variable.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -0px}.tsd-kind-variable.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -0px}.tsd-kind-variable.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -0px}.tsd-kind-variable.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -0px}.tsd-kind-property>.tsd-kind-icon:before{background-position:-136px -0px}.tsd-kind-property.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -0px}.tsd-kind-property.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -0px}.tsd-kind-property.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -0px}.tsd-kind-property.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -0px}.tsd-kind-property.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -0px}.tsd-kind-property.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -0px}.tsd-kind-property.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -0px}.tsd-kind-property.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -0px}.tsd-kind-property.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -0px}.tsd-kind-property.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -0px}.tsd-kind-property.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -0px}.tsd-kind-property.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -0px}.tsd-kind-get-signature>.tsd-kind-icon:before{background-position:-136px -17px}.tsd-kind-get-signature.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -17px}.tsd-kind-get-signature.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -17px}.tsd-kind-get-signature.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -17px}.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -17px}.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -17px}.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -17px}.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -17px}.tsd-kind-get-signature.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -17px}.tsd-kind-get-signature.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -17px}.tsd-kind-get-signature.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -17px}.tsd-kind-get-signature.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -17px}.tsd-kind-get-signature.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -17px}.tsd-kind-set-signature>.tsd-kind-icon:before{background-position:-136px -34px}.tsd-kind-set-signature.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -34px}.tsd-kind-set-signature.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -34px}.tsd-kind-set-signature.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -34px}.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -34px}.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -34px}.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -34px}.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -34px}.tsd-kind-set-signature.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -34px}.tsd-kind-set-signature.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -34px}.tsd-kind-set-signature.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -34px}.tsd-kind-set-signature.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -34px}.tsd-kind-set-signature.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -34px}.tsd-kind-accessor>.tsd-kind-icon:before{background-position:-136px -51px}.tsd-kind-accessor.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -51px}.tsd-kind-accessor.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -51px}.tsd-kind-accessor.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -51px}.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -51px}.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -51px}.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -51px}.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -51px}.tsd-kind-accessor.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -51px}.tsd-kind-accessor.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -51px}.tsd-kind-accessor.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -51px}.tsd-kind-accessor.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -51px}.tsd-kind-accessor.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -51px}.tsd-kind-function>.tsd-kind-icon:before{background-position:-136px -68px}.tsd-kind-function.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -68px}.tsd-kind-function.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -68px}.tsd-kind-function.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -68px}.tsd-kind-function.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -68px}.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -68px}.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -68px}.tsd-kind-function.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -68px}.tsd-kind-function.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -68px}.tsd-kind-function.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -68px}.tsd-kind-function.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -68px}.tsd-kind-function.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -68px}.tsd-kind-function.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -68px}.tsd-kind-method>.tsd-kind-icon:before{background-position:-136px -68px}.tsd-kind-method.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -68px}.tsd-kind-method.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -68px}.tsd-kind-method.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -68px}.tsd-kind-method.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -68px}.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -68px}.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -68px}.tsd-kind-method.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -68px}.tsd-kind-method.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -68px}.tsd-kind-method.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -68px}.tsd-kind-method.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -68px}.tsd-kind-method.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -68px}.tsd-kind-method.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -68px}.tsd-kind-call-signature>.tsd-kind-icon:before{background-position:-136px -68px}.tsd-kind-call-signature.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -68px}.tsd-kind-call-signature.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -68px}.tsd-kind-call-signature.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -68px}.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -68px}.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -68px}.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -68px}.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -68px}.tsd-kind-call-signature.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -68px}.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -68px}.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -68px}.tsd-kind-call-signature.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -68px}.tsd-kind-call-signature.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -68px}.tsd-kind-function.tsd-has-type-parameter>.tsd-kind-icon:before{background-position:-136px -85px}.tsd-kind-function.tsd-has-type-parameter.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -85px}.tsd-kind-function.tsd-has-type-parameter.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -85px}.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -85px}.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -85px}.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -85px}.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -85px}.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -85px}.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -85px}.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -85px}.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -85px}.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -85px}.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -85px}.tsd-kind-method.tsd-has-type-parameter>.tsd-kind-icon:before{background-position:-136px -85px}.tsd-kind-method.tsd-has-type-parameter.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -85px}.tsd-kind-method.tsd-has-type-parameter.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -85px}.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -85px}.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -85px}.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -85px}.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -85px}.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -85px}.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -85px}.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -85px}.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -85px}.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -85px}.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -85px}.tsd-kind-constructor>.tsd-kind-icon:before{background-position:-136px -102px}.tsd-kind-constructor.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -102px}.tsd-kind-constructor.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -102px}.tsd-kind-constructor.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -102px}.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -102px}.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -102px}.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -102px}.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -102px}.tsd-kind-constructor.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -102px}.tsd-kind-constructor.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -102px}.tsd-kind-constructor.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -102px}.tsd-kind-constructor.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -102px}.tsd-kind-constructor.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -102px}.tsd-kind-constructor-signature>.tsd-kind-icon:before{background-position:-136px -102px}.tsd-kind-constructor-signature.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -102px}.tsd-kind-constructor-signature.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -102px}.tsd-kind-constructor-signature.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -102px}.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -102px}.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -102px}.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -102px}.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -102px}.tsd-kind-constructor-signature.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -102px}.tsd-kind-constructor-signature.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -102px}.tsd-kind-constructor-signature.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -102px}.tsd-kind-constructor-signature.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -102px}.tsd-kind-constructor-signature.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -102px}.tsd-kind-index-signature>.tsd-kind-icon:before{background-position:-136px -119px}.tsd-kind-index-signature.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -119px}.tsd-kind-index-signature.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -119px}.tsd-kind-index-signature.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -119px}.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -119px}.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -119px}.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -119px}.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -119px}.tsd-kind-index-signature.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -119px}.tsd-kind-index-signature.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -119px}.tsd-kind-index-signature.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -119px}.tsd-kind-index-signature.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -119px}.tsd-kind-index-signature.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -119px}.tsd-kind-event>.tsd-kind-icon:before{background-position:-136px -136px}.tsd-kind-event.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -136px}.tsd-kind-event.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -136px}.tsd-kind-event.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -136px}.tsd-kind-event.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -136px}.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -136px}.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -136px}.tsd-kind-event.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -136px}.tsd-kind-event.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -136px}.tsd-kind-event.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -136px}.tsd-kind-event.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -136px}.tsd-kind-event.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -136px}.tsd-kind-event.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -136px}.tsd-is-static>.tsd-kind-icon:before{background-position:-136px -153px}.tsd-is-static.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -153px}.tsd-is-static.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -153px}.tsd-is-static.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -153px}.tsd-is-static.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -153px}.tsd-is-static.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -153px}.tsd-is-static.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -153px}.tsd-is-static.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -153px}.tsd-is-static.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -153px}.tsd-is-static.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -153px}.tsd-is-static.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -153px}.tsd-is-static.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -153px}.tsd-is-static.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -153px}.tsd-is-static.tsd-kind-function>.tsd-kind-icon:before{background-position:-136px -170px}.tsd-is-static.tsd-kind-function.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -170px}.tsd-is-static.tsd-kind-function.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -170px}.tsd-is-static.tsd-kind-function.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -170px}.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -170px}.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -170px}.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -170px}.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -170px}.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -170px}.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -170px}.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -170px}.tsd-is-static.tsd-kind-function.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -170px}.tsd-is-static.tsd-kind-function.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -170px}.tsd-is-static.tsd-kind-method>.tsd-kind-icon:before{background-position:-136px -170px}.tsd-is-static.tsd-kind-method.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -170px}.tsd-is-static.tsd-kind-method.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -170px}.tsd-is-static.tsd-kind-method.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -170px}.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -170px}.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -170px}.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -170px}.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -170px}.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -170px}.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -170px}.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -170px}.tsd-is-static.tsd-kind-method.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -170px}.tsd-is-static.tsd-kind-method.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -170px}.tsd-is-static.tsd-kind-call-signature>.tsd-kind-icon:before{background-position:-136px -170px}.tsd-is-static.tsd-kind-call-signature.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -170px}.tsd-is-static.tsd-kind-call-signature.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -170px}.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -170px}.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -170px}.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -170px}.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -170px}.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -170px}.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -170px}.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -170px}.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -170px}.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -170px}.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -170px}.tsd-is-static.tsd-kind-event>.tsd-kind-icon:before{background-position:-136px -187px}.tsd-is-static.tsd-kind-event.tsd-is-protected>.tsd-kind-icon:before{background-position:-153px -187px}.tsd-is-static.tsd-kind-event.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -187px}.tsd-is-static.tsd-kind-event.tsd-parent-kind-class>.tsd-kind-icon:before{background-position:-51px -187px}.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-inherited>.tsd-kind-icon:before{background-position:-68px -187px}.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected>.tsd-kind-icon:before{background-position:-85px -187px}.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited>.tsd-kind-icon:before{background-position:-102px -187px}.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -187px}.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum>.tsd-kind-icon:before{background-position:-170px -187px}.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum.tsd-is-protected>.tsd-kind-icon:before{background-position:-187px -187px}.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum.tsd-is-private>.tsd-kind-icon:before{background-position:-119px -187px}.tsd-is-static.tsd-kind-event.tsd-parent-kind-interface>.tsd-kind-icon:before{background-position:-204px -187px}.tsd-is-static.tsd-kind-event.tsd-parent-kind-interface.tsd-is-inherited>.tsd-kind-icon:before{background-position:-221px -187px}@keyframes fade-in{from{opacity:0}to{opacity:1}}@keyframes fade-out{from{opacity:1;visibility:visible}to{opacity:0}}@keyframes fade-in-delayed{0%{opacity:0}33%{opacity:0}100%{opacity:1}}@keyframes fade-out-delayed{0%{opacity:1;visibility:visible}66%{opacity:0}100%{opacity:0}}@keyframes shift-to-left{from{transform:translate(0, 0)}to{transform:translate(-25%, 0)}}@keyframes unshift-to-left{from{transform:translate(-25%, 0)}to{transform:translate(0, 0)}}@keyframes pop-in-from-right{from{transform:translate(100%, 0)}to{transform:translate(0, 0)}}@keyframes pop-out-to-right{from{transform:translate(0, 0);visibility:visible}to{transform:translate(100%, 0)}}body{background:#fdfdfd;font-family:"Segoe UI",sans-serif;font-size:16px;color:#222}a{color:#4da6ff;text-decoration:none}a:hover{text-decoration:underline}code,pre{font-family:Menlo,Monaco,Consolas,"Courier New",monospace;padding:.2em;margin:0;font-size:14px;background-color:rgba(0,0,0,.04)}pre{padding:10px}pre code{padding:0;font-size:100%;background-color:transparent}.tsd-typography{line-height:1.333em}.tsd-typography ul{list-style:square;padding:0 0 0 20px;margin:0}.tsd-typography h4,.tsd-typography .tsd-index-panel h3,.tsd-index-panel .tsd-typography h3,.tsd-typography h5,.tsd-typography h6{font-size:1em;margin:0}.tsd-typography h5,.tsd-typography h6{font-weight:normal}.tsd-typography p,.tsd-typography ul,.tsd-typography ol{margin:1em 0}@media(min-width: 901px)and (max-width: 1024px){html.default .col-content{width:72%}html.default .col-menu{width:28%}html.default .tsd-navigation{padding-left:10px}}@media(max-width: 900px){html.default .col-content{float:none;width:100%}html.default .col-menu{position:fixed !important;overflow:auto;-webkit-overflow-scrolling:touch;z-index:1024;top:0 !important;bottom:0 !important;left:auto !important;right:0 !important;width:100%;padding:20px 20px 0 0;max-width:450px;visibility:hidden;background-color:#fff;transform:translate(100%, 0)}html.default .col-menu>*:last-child{padding-bottom:20px}html.default .overlay{content:"";display:block;position:fixed;z-index:1023;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,.75);visibility:hidden}html.default.to-has-menu .overlay{animation:fade-in .4s}html.default.to-has-menu header,html.default.to-has-menu footer,html.default.to-has-menu .col-content{animation:shift-to-left .4s}html.default.to-has-menu .col-menu{animation:pop-in-from-right .4s}html.default.from-has-menu .overlay{animation:fade-out .4s}html.default.from-has-menu header,html.default.from-has-menu footer,html.default.from-has-menu .col-content{animation:unshift-to-left .4s}html.default.from-has-menu .col-menu{animation:pop-out-to-right .4s}html.default.has-menu body{overflow:hidden}html.default.has-menu .overlay{visibility:visible}html.default.has-menu header,html.default.has-menu footer,html.default.has-menu .col-content{transform:translate(-25%, 0)}html.default.has-menu .col-menu{visibility:visible;transform:translate(0, 0)}}.tsd-page-title{padding:70px 0 20px 0;margin:0 0 40px 0;background:#fff;box-shadow:0 0 5px rgba(0,0,0,.35)}.tsd-page-title h1{margin:0}.tsd-breadcrumb{margin:0;padding:0;color:#707070}.tsd-breadcrumb a{color:#707070;text-decoration:none}.tsd-breadcrumb a:hover{text-decoration:underline}.tsd-breadcrumb li{display:inline}.tsd-breadcrumb li:after{content:" / "}html.minimal .container{margin:0}html.minimal .container-main{padding-top:50px;padding-bottom:0}html.minimal .content-wrap{padding-left:300px}html.minimal .tsd-navigation{position:fixed !important;overflow:auto;-webkit-overflow-scrolling:touch;box-sizing:border-box;z-index:1;left:0;top:40px;bottom:0;width:300px;padding:20px;margin:0}html.minimal .tsd-member .tsd-member{margin-left:0}html.minimal .tsd-page-toolbar{position:fixed;z-index:2}html.minimal #tsd-filter .tsd-filter-group{right:0;transform:none}html.minimal footer{background-color:transparent}html.minimal footer .container{padding:0}html.minimal .tsd-generator{padding:0}@media(max-width: 900px){html.minimal .tsd-navigation{display:none}html.minimal .content-wrap{padding-left:0}}dl.tsd-comment-tags{overflow:hidden}dl.tsd-comment-tags dt{float:left;padding:1px 5px;margin:0 10px 0 0;border-radius:4px;border:1px solid #707070;color:#707070;font-size:.8em;font-weight:normal}dl.tsd-comment-tags dd{margin:0 0 10px 0}dl.tsd-comment-tags dd:before,dl.tsd-comment-tags dd:after{display:table;content:" "}dl.tsd-comment-tags dd pre,dl.tsd-comment-tags dd:after{clear:both}dl.tsd-comment-tags p{margin:0}.tsd-panel.tsd-comment .lead{font-size:1.1em;line-height:1.333em;margin-bottom:2em}.tsd-panel.tsd-comment .lead:last-child{margin-bottom:0}.toggle-protected .tsd-is-private{display:none}.toggle-public .tsd-is-private,.toggle-public .tsd-is-protected,.toggle-public .tsd-is-private-protected{display:none}.toggle-inherited .tsd-is-inherited{display:none}.toggle-only-exported .tsd-is-not-exported{display:none}.toggle-externals .tsd-is-external{display:none}#tsd-filter{position:relative;display:inline-block;height:40px;vertical-align:bottom}.no-filter #tsd-filter{display:none}#tsd-filter .tsd-filter-group{display:inline-block;height:40px;vertical-align:bottom;white-space:nowrap}#tsd-filter input{display:none}@media(max-width: 900px){#tsd-filter .tsd-filter-group{display:block;position:absolute;top:40px;right:20px;height:auto;background-color:#fff;visibility:hidden;transform:translate(50%, 0);box-shadow:0 0 4px rgba(0,0,0,.25)}.has-options #tsd-filter .tsd-filter-group{visibility:visible}.to-has-options #tsd-filter .tsd-filter-group{animation:fade-in .2s}.from-has-options #tsd-filter .tsd-filter-group{animation:fade-out .2s}#tsd-filter label,#tsd-filter .tsd-select{display:block;padding-right:20px}}footer{border-top:1px solid #eee;background-color:#fff}footer.with-border-bottom{border-bottom:1px solid #eee}footer .tsd-legend-group{font-size:0}footer .tsd-legend{display:inline-block;width:25%;padding:0;font-size:16px;list-style:none;line-height:1.333em;vertical-align:top}@media(max-width: 900px){footer .tsd-legend{width:50%}}.tsd-hierarchy{list-style:square;padding:0 0 0 20px;margin:0}.tsd-hierarchy .target{font-weight:bold}.tsd-index-panel .tsd-index-content{margin-bottom:-30px !important}.tsd-index-panel .tsd-index-section{margin-bottom:30px !important}.tsd-index-panel h3{margin:0 -20px 10px -20px;padding:0 20px 10px 20px;border-bottom:1px solid #eee}.tsd-index-panel ul.tsd-index-list{-webkit-column-count:3;-moz-column-count:3;-ms-column-count:3;-o-column-count:3;column-count:3;-webkit-column-gap:20px;-moz-column-gap:20px;-ms-column-gap:20px;-o-column-gap:20px;column-gap:20px;padding:0;list-style:none;line-height:1.333em}@media(max-width: 900px){.tsd-index-panel ul.tsd-index-list{-webkit-column-count:1;-moz-column-count:1;-ms-column-count:1;-o-column-count:1;column-count:1}}@media(min-width: 901px)and (max-width: 1024px){.tsd-index-panel ul.tsd-index-list{-webkit-column-count:2;-moz-column-count:2;-ms-column-count:2;-o-column-count:2;column-count:2}}.tsd-index-panel ul.tsd-index-list li{-webkit-page-break-inside:avoid;-moz-page-break-inside:avoid;-ms-page-break-inside:avoid;-o-page-break-inside:avoid;page-break-inside:avoid}.tsd-index-panel a,.tsd-index-panel .tsd-parent-kind-module a{color:#9600ff}.tsd-index-panel .tsd-parent-kind-interface a{color:#647f1b}.tsd-index-panel .tsd-parent-kind-enum a{color:#937210}.tsd-index-panel .tsd-parent-kind-class a{color:#0672de}.tsd-index-panel .tsd-kind-module a{color:#9600ff}.tsd-index-panel .tsd-kind-interface a{color:#647f1b}.tsd-index-panel .tsd-kind-enum a{color:#937210}.tsd-index-panel .tsd-kind-class a{color:#0672de}.tsd-index-panel .tsd-is-private a{color:#707070}.tsd-flag{display:inline-block;padding:1px 5px;border-radius:4px;color:#fff;background-color:#707070;text-indent:0;font-size:14px;font-weight:normal}.tsd-anchor{position:absolute;top:-100px}.tsd-member{position:relative}.tsd-member .tsd-anchor+h3{margin-top:0;margin-bottom:0;border-bottom:none}.tsd-navigation{margin:0 0 0 40px}.tsd-navigation a{display:block;padding-top:2px;padding-bottom:2px;border-left:2px solid transparent;color:#222;text-decoration:none;transition:border-left-color .1s}.tsd-navigation a:hover{text-decoration:underline}.tsd-navigation ul{margin:0;padding:0;list-style:none}.tsd-navigation li{padding:0}.tsd-navigation.primary{padding-bottom:40px}.tsd-navigation.primary a{display:block;padding-top:6px;padding-bottom:6px}.tsd-navigation.primary ul li a{padding-left:5px}.tsd-navigation.primary ul li li a{padding-left:25px}.tsd-navigation.primary ul li li li a{padding-left:45px}.tsd-navigation.primary ul li li li li a{padding-left:65px}.tsd-navigation.primary ul li li li li li a{padding-left:85px}.tsd-navigation.primary ul li li li li li li a{padding-left:105px}.tsd-navigation.primary>ul{border-bottom:1px solid #eee}.tsd-navigation.primary li{border-top:1px solid #eee}.tsd-navigation.primary li.current>a{font-weight:bold}.tsd-navigation.primary li.label span{display:block;padding:20px 0 6px 5px;color:#707070}.tsd-navigation.primary li.globals+li>span,.tsd-navigation.primary li.globals+li>a{padding-top:20px}.tsd-navigation.secondary{max-height:calc(100vh - 1rem - 40px);overflow:auto;position:-webkit-sticky;position:sticky;top:calc(.5rem + 40px);transition:.3s}.tsd-navigation.secondary.tsd-navigation--toolbar-hide{max-height:calc(100vh - 1rem);top:.5rem}.tsd-navigation.secondary ul{transition:opacity .2s}.tsd-navigation.secondary ul li a{padding-left:25px}.tsd-navigation.secondary ul li li a{padding-left:45px}.tsd-navigation.secondary ul li li li a{padding-left:65px}.tsd-navigation.secondary ul li li li li a{padding-left:85px}.tsd-navigation.secondary ul li li li li li a{padding-left:105px}.tsd-navigation.secondary ul li li li li li li a{padding-left:125px}.tsd-navigation.secondary ul.current a{border-left-color:#eee}.tsd-navigation.secondary li.focus>a,.tsd-navigation.secondary ul.current li.focus>a{border-left-color:#000}.tsd-navigation.secondary li.current{margin-top:20px;margin-bottom:20px;border-left-color:#eee}.tsd-navigation.secondary li.current>a{font-weight:bold}@media(min-width: 901px){.menu-sticky-wrap{position:static}}.tsd-panel{margin:20px 0;padding:20px;background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.25)}.tsd-panel:empty{display:none}.tsd-panel>h1,.tsd-panel>h2,.tsd-panel>h3{margin:1.5em -20px 10px -20px;padding:0 20px 10px 20px;border-bottom:1px solid #eee}.tsd-panel>h1.tsd-before-signature,.tsd-panel>h2.tsd-before-signature,.tsd-panel>h3.tsd-before-signature{margin-bottom:0;border-bottom:0}.tsd-panel table{display:block;width:100%;overflow:auto;margin-top:10px;word-break:normal;word-break:keep-all}.tsd-panel table th{font-weight:bold}.tsd-panel table th,.tsd-panel table td{padding:6px 13px;border:1px solid #ddd}.tsd-panel table tr{background-color:#fff;border-top:1px solid #ccc}.tsd-panel table tr:nth-child(2n){background-color:#f8f8f8}.tsd-panel-group{margin:60px 0}.tsd-panel-group>h1,.tsd-panel-group>h2,.tsd-panel-group>h3{padding-left:20px;padding-right:20px}#tsd-search{transition:background-color .2s}#tsd-search .title{position:relative;z-index:2}#tsd-search .field{position:absolute;left:0;top:0;right:40px;height:40px}#tsd-search .field input{box-sizing:border-box;position:relative;top:-50px;z-index:1;width:100%;padding:0 10px;opacity:0;outline:0;border:0;background:transparent;color:#222}#tsd-search .field label{position:absolute;overflow:hidden;right:-40px}#tsd-search .field input,#tsd-search .title{transition:opacity .2s}#tsd-search .results{position:absolute;visibility:hidden;top:40px;width:100%;margin:0;padding:0;list-style:none;box-shadow:0 0 4px rgba(0,0,0,.25)}#tsd-search .results li{padding:0 10px;background-color:#fdfdfd}#tsd-search .results li:nth-child(even){background-color:#fff}#tsd-search .results li.state{display:none}#tsd-search .results li.current,#tsd-search .results li:hover{background-color:#eee}#tsd-search .results a{display:block}#tsd-search .results a:before{top:10px}#tsd-search .results span.parent{color:#707070;font-weight:normal}#tsd-search.has-focus{background-color:#eee}#tsd-search.has-focus .field input{top:0;opacity:1}#tsd-search.has-focus .title{z-index:0;opacity:0}#tsd-search.has-focus .results{visibility:visible}#tsd-search.loading .results li.state.loading{display:block}#tsd-search.failure .results li.state.failure{display:block}.tsd-signature{margin:0 0 1em 0;padding:10px;border:1px solid #eee;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:14px;overflow-x:auto}.tsd-signature.tsd-kind-icon{padding-left:30px}.tsd-signature.tsd-kind-icon:before{top:10px;left:10px}.tsd-panel>.tsd-signature{margin-left:-20px;margin-right:-20px;border-width:1px 0}.tsd-panel>.tsd-signature.tsd-kind-icon{padding-left:40px}.tsd-panel>.tsd-signature.tsd-kind-icon:before{left:20px}.tsd-signature-symbol{color:#707070;font-weight:normal}.tsd-signature-type{font-style:italic;font-weight:normal}.tsd-signatures{padding:0;margin:0 0 1em 0;border:1px solid #eee}.tsd-signatures .tsd-signature{margin:0;border-width:1px 0 0 0;transition:background-color .1s}.tsd-signatures .tsd-signature:first-child{border-top-width:0}.tsd-signatures .tsd-signature.current{background-color:#eee}.tsd-signatures.active>.tsd-signature{cursor:pointer}.tsd-panel>.tsd-signatures{margin-left:-20px;margin-right:-20px;border-width:1px 0}.tsd-panel>.tsd-signatures .tsd-signature.tsd-kind-icon{padding-left:40px}.tsd-panel>.tsd-signatures .tsd-signature.tsd-kind-icon:before{left:20px}.tsd-panel>a.anchor+.tsd-signatures{border-top-width:0;margin-top:-20px}ul.tsd-descriptions{position:relative;overflow:hidden;padding:0;list-style:none}ul.tsd-descriptions.active>.tsd-description{display:none}ul.tsd-descriptions.active>.tsd-description.current{display:block}ul.tsd-descriptions.active>.tsd-description.fade-in{animation:fade-in-delayed .3s}ul.tsd-descriptions.active>.tsd-description.fade-out{animation:fade-out-delayed .3s;position:absolute;display:block;top:0;left:0;right:0;opacity:0;visibility:hidden}ul.tsd-descriptions h4,ul.tsd-descriptions .tsd-index-panel h3,.tsd-index-panel ul.tsd-descriptions h3{font-size:16px;margin:1em 0 .5em 0}ul.tsd-parameters,ul.tsd-type-parameters{list-style:square;margin:0;padding-left:20px}ul.tsd-parameters>li.tsd-parameter-signature,ul.tsd-type-parameters>li.tsd-parameter-signature{list-style:none;margin-left:-20px}ul.tsd-parameters h5,ul.tsd-type-parameters h5{font-size:16px;margin:1em 0 .5em 0}ul.tsd-parameters .tsd-comment,ul.tsd-type-parameters .tsd-comment{margin-top:-0.5em}.tsd-sources{font-size:14px;color:#707070;margin:0 0 1em 0}.tsd-sources a{color:#707070;text-decoration:underline}.tsd-sources ul,.tsd-sources p{margin:0 !important}.tsd-sources ul{list-style:none;padding:0}.tsd-page-toolbar{position:fixed;z-index:1;top:0;left:0;width:100%;height:40px;color:#333;background:#fff;border-bottom:1px solid #eee;transition:transform .3s linear}.tsd-page-toolbar a{color:#333;text-decoration:none}.tsd-page-toolbar a.title{font-weight:bold}.tsd-page-toolbar a.title:hover{text-decoration:underline}.tsd-page-toolbar .table-wrap{display:table;width:100%;height:40px}.tsd-page-toolbar .table-cell{display:table-cell;position:relative;white-space:nowrap;line-height:40px}.tsd-page-toolbar .table-cell:first-child{width:100%}.tsd-page-toolbar--hide{transform:translateY(-100%)}.tsd-select .tsd-select-list li:before,.tsd-select .tsd-select-label:before,.tsd-widget:before{content:"";display:inline-block;width:40px;height:40px;margin:0 -8px 0 0;background-image:url(../images/widgets.png);background-repeat:no-repeat;text-indent:-1024px;vertical-align:bottom}@media(-webkit-min-device-pixel-ratio: 1.5),(min-resolution: 144dpi){.tsd-select .tsd-select-list li:before,.tsd-select .tsd-select-label:before,.tsd-widget:before{background-image:url(../images/widgets@2x.png);background-size:320px 40px}}.tsd-widget{display:inline-block;overflow:hidden;opacity:.6;height:40px;transition:opacity .1s,background-color .2s;vertical-align:bottom;cursor:pointer}.tsd-widget:hover{opacity:.8}.tsd-widget.active{opacity:1;background-color:#eee}.tsd-widget.no-caption{width:40px}.tsd-widget.no-caption:before{margin:0}.tsd-widget.search:before{background-position:0 0}.tsd-widget.menu:before{background-position:-40px 0}.tsd-widget.options:before{background-position:-80px 0}.tsd-widget.options,.tsd-widget.menu{display:none}@media(max-width: 900px){.tsd-widget.options,.tsd-widget.menu{display:inline-block}}input[type=checkbox]+.tsd-widget:before{background-position:-120px 0}input[type=checkbox]:checked+.tsd-widget:before{background-position:-160px 0}.tsd-select{position:relative;display:inline-block;height:40px;transition:opacity .1s,background-color .2s;vertical-align:bottom;cursor:pointer}.tsd-select .tsd-select-label{opacity:.6;transition:opacity .2s}.tsd-select .tsd-select-label:before{background-position:-240px 0}.tsd-select.active .tsd-select-label{opacity:.8}.tsd-select.active .tsd-select-list{visibility:visible;opacity:1;transition-delay:0s}.tsd-select .tsd-select-list{position:absolute;visibility:hidden;top:40px;left:0;margin:0;padding:0;opacity:0;list-style:none;box-shadow:0 0 4px rgba(0,0,0,.25);transition:visibility 0s .2s,opacity .2s}.tsd-select .tsd-select-list li{padding:0 20px 0 0;background-color:#fdfdfd}.tsd-select .tsd-select-list li:before{background-position:40px 0}.tsd-select .tsd-select-list li:nth-child(even){background-color:#fff}.tsd-select .tsd-select-list li:hover{background-color:#eee}.tsd-select .tsd-select-list li.selected:before{background-position:-200px 0}@media(max-width: 900px){.tsd-select .tsd-select-list{top:0;left:auto;right:100%;margin-right:-5px}.tsd-select .tsd-select-label:before{background-position:-280px 0}}img{max-width:100%} 2 | -------------------------------------------------------------------------------- /docs/assets/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmorten/superoak/8fa1820fda29b97021d60ea5756ccfa07616325c/docs/assets/images/icons.png -------------------------------------------------------------------------------- /docs/assets/images/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmorten/superoak/8fa1820fda29b97021d60ea5756ccfa07616325c/docs/assets/images/icons@2x.png -------------------------------------------------------------------------------- /docs/assets/images/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmorten/superoak/8fa1820fda29b97021d60ea5756ccfa07616325c/docs/assets/images/widgets.png -------------------------------------------------------------------------------- /docs/assets/images/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmorten/superoak/8fa1820fda29b97021d60ea5756ccfa07616325c/docs/assets/images/widgets@2x.png -------------------------------------------------------------------------------- /docs/assets/js/main.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)r.d(n,i,function(t){return e[t]}.bind(null,i));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=2)}([function(e,t,r){var n,i; 2 | /** 3 | * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 4 | * Copyright (C) 2020 Oliver Nightingale 5 | * @license MIT 6 | */!function(){var s,o,a,u,l,c,h,d,f,p,y,m,v,g,x,w,L,E,b,S,k,Q,O,P,T,_,C=function(e){var t=new C.Builder;return t.pipeline.add(C.trimmer,C.stopWordFilter,C.stemmer),t.searchPipeline.add(C.stemmer),e.call(t,t),t.build()};C.version="2.3.9" 7 | /*! 8 | * lunr.utils 9 | * Copyright (C) 2020 Oliver Nightingale 10 | */,C.utils={},C.utils.warn=(s=this,function(e){s.console&&console.warn&&console.warn(e)}),C.utils.asString=function(e){return null==e?"":e.toString()},C.utils.clone=function(e){if(null==e)return e;for(var t=Object.create(null),r=Object.keys(e),n=0;n0){var u=C.utils.clone(t)||{};u.position=[o,a],u.index=i.length,i.push(new C.Token(r.slice(o,s),u))}o=s+1}}return i},C.tokenizer.separator=/[\s\-]+/ 19 | /*! 20 | * lunr.Pipeline 21 | * Copyright (C) 2020 Oliver Nightingale 22 | */,C.Pipeline=function(){this._stack=[]},C.Pipeline.registeredFunctions=Object.create(null),C.Pipeline.registerFunction=function(e,t){t in this.registeredFunctions&&C.utils.warn("Overwriting existing registered function: "+t),e.label=t,C.Pipeline.registeredFunctions[e.label]=e},C.Pipeline.warnIfFunctionNotRegistered=function(e){e.label&&e.label in this.registeredFunctions||C.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},C.Pipeline.load=function(e){var t=new C.Pipeline;return e.forEach((function(e){var r=C.Pipeline.registeredFunctions[e];if(!r)throw new Error("Cannot load unregistered function: "+e);t.add(r)})),t},C.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach((function(e){C.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)}),this)},C.Pipeline.prototype.after=function(e,t){C.Pipeline.warnIfFunctionNotRegistered(t);var r=this._stack.indexOf(e);if(-1==r)throw new Error("Cannot find existingFn");r+=1,this._stack.splice(r,0,t)},C.Pipeline.prototype.before=function(e,t){C.Pipeline.warnIfFunctionNotRegistered(t);var r=this._stack.indexOf(e);if(-1==r)throw new Error("Cannot find existingFn");this._stack.splice(r,0,t)},C.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);-1!=t&&this._stack.splice(t,1)},C.Pipeline.prototype.run=function(e){for(var t=this._stack.length,r=0;r1&&(se&&(r=i),s!=e);)n=r-t,i=t+Math.floor(n/2),s=this.elements[2*i];return s==e||s>e?2*i:sa?l+=2:o==a&&(t+=r[u+1]*n[l+1],u+=2,l+=2);return t},C.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},C.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,r=0;t0){var s,o=i.str.charAt(0);o in i.node.edges?s=i.node.edges[o]:(s=new C.TokenSet,i.node.edges[o]=s),1==i.str.length&&(s.final=!0),n.push({node:s,editsRemaining:i.editsRemaining,str:i.str.slice(1)})}if(0!=i.editsRemaining){if("*"in i.node.edges)var a=i.node.edges["*"];else{a=new C.TokenSet;i.node.edges["*"]=a}if(0==i.str.length&&(a.final=!0),n.push({node:a,editsRemaining:i.editsRemaining-1,str:i.str}),i.str.length>1&&n.push({node:i.node,editsRemaining:i.editsRemaining-1,str:i.str.slice(1)}),1==i.str.length&&(i.node.final=!0),i.str.length>=1){if("*"in i.node.edges)var u=i.node.edges["*"];else{u=new C.TokenSet;i.node.edges["*"]=u}1==i.str.length&&(u.final=!0),n.push({node:u,editsRemaining:i.editsRemaining-1,str:i.str.slice(1)})}if(i.str.length>1){var l,c=i.str.charAt(0),h=i.str.charAt(1);h in i.node.edges?l=i.node.edges[h]:(l=new C.TokenSet,i.node.edges[h]=l),1==i.str.length&&(l.final=!0),n.push({node:l,editsRemaining:i.editsRemaining-1,str:c+i.str.slice(2)})}}}return r},C.TokenSet.fromString=function(e){for(var t=new C.TokenSet,r=t,n=0,i=e.length;n=e;t--){var r=this.uncheckedNodes[t],n=r.child.toString();n in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[n]:(r.child._str=n,this.minimizedNodes[n]=r.child),this.uncheckedNodes.pop()}} 44 | /*! 45 | * lunr.Index 46 | * Copyright (C) 2020 Oliver Nightingale 47 | */,C.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},C.Index.prototype.search=function(e){return this.query((function(t){new C.QueryParser(e,t).parse()}))},C.Index.prototype.query=function(e){for(var t=new C.Query(this.fields),r=Object.create(null),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=0;a1?1:e},C.Builder.prototype.k1=function(e){this._k1=e},C.Builder.prototype.add=function(e,t){var r=e[this._ref],n=Object.keys(this._fields);this._documents[r]=t||{},this.documentCount+=1;for(var i=0;i=this.length)return C.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},C.QueryLexer.prototype.width=function(){return this.pos-this.start},C.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},C.QueryLexer.prototype.backup=function(){this.pos-=1},C.QueryLexer.prototype.acceptDigitRun=function(){var e,t;do{t=(e=this.next()).charCodeAt(0)}while(t>47&&t<58);e!=C.QueryLexer.EOS&&this.backup()},C.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(C.QueryLexer.TERM)),e.ignore(),e.more())return C.QueryLexer.lexText},C.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(C.QueryLexer.EDIT_DISTANCE),C.QueryLexer.lexText},C.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(C.QueryLexer.BOOST),C.QueryLexer.lexText},C.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(C.QueryLexer.TERM)},C.QueryLexer.termSeparator=C.tokenizer.separator,C.QueryLexer.lexText=function(e){for(;;){var t=e.next();if(t==C.QueryLexer.EOS)return C.QueryLexer.lexEOS;if(92!=t.charCodeAt(0)){if(":"==t)return C.QueryLexer.lexField;if("~"==t)return e.backup(),e.width()>0&&e.emit(C.QueryLexer.TERM),C.QueryLexer.lexEditDistance;if("^"==t)return e.backup(),e.width()>0&&e.emit(C.QueryLexer.TERM),C.QueryLexer.lexBoost;if("+"==t&&1===e.width())return e.emit(C.QueryLexer.PRESENCE),C.QueryLexer.lexText;if("-"==t&&1===e.width())return e.emit(C.QueryLexer.PRESENCE),C.QueryLexer.lexText;if(t.match(C.QueryLexer.termSeparator))return C.QueryLexer.lexTerm}else e.escapeCharacter()}},C.QueryParser=function(e,t){this.lexer=new C.QueryLexer(e),this.query=t,this.currentClause={},this.lexemeIdx=0},C.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=C.QueryParser.parseClause;e;)e=e(this);return this.query},C.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},C.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},C.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},C.QueryParser.parseClause=function(e){var t=e.peekLexeme();if(null!=t)switch(t.type){case C.QueryLexer.PRESENCE:return C.QueryParser.parsePresence;case C.QueryLexer.FIELD:return C.QueryParser.parseField;case C.QueryLexer.TERM:return C.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+t.type;throw t.str.length>=1&&(r+=" with value '"+t.str+"'"),new C.QueryParseError(r,t.start,t.end)}},C.QueryParser.parsePresence=function(e){var t=e.consumeLexeme();if(null!=t){switch(t.str){case"-":e.currentClause.presence=C.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=C.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+t.str+"'";throw new C.QueryParseError(r,t.start,t.end)}var n=e.peekLexeme();if(null==n){r="expecting term or field, found nothing";throw new C.QueryParseError(r,t.start,t.end)}switch(n.type){case C.QueryLexer.FIELD:return C.QueryParser.parseField;case C.QueryLexer.TERM:return C.QueryParser.parseTerm;default:r="expecting term or field, found '"+n.type+"'";throw new C.QueryParseError(r,n.start,n.end)}}},C.QueryParser.parseField=function(e){var t=e.consumeLexeme();if(null!=t){if(-1==e.query.allFields.indexOf(t.str)){var r=e.query.allFields.map((function(e){return"'"+e+"'"})).join(", "),n="unrecognised field '"+t.str+"', possible fields: "+r;throw new C.QueryParseError(n,t.start,t.end)}e.currentClause.fields=[t.str];var i=e.peekLexeme();if(null==i){n="expecting term, found nothing";throw new C.QueryParseError(n,t.start,t.end)}switch(i.type){case C.QueryLexer.TERM:return C.QueryParser.parseTerm;default:n="expecting term, found '"+i.type+"'";throw new C.QueryParseError(n,i.start,i.end)}}},C.QueryParser.parseTerm=function(e){var t=e.consumeLexeme();if(null!=t){e.currentClause.term=t.str.toLowerCase(),-1!=t.str.indexOf("*")&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(null!=r)switch(r.type){case C.QueryLexer.TERM:return e.nextClause(),C.QueryParser.parseTerm;case C.QueryLexer.FIELD:return e.nextClause(),C.QueryParser.parseField;case C.QueryLexer.EDIT_DISTANCE:return C.QueryParser.parseEditDistance;case C.QueryLexer.BOOST:return C.QueryParser.parseBoost;case C.QueryLexer.PRESENCE:return e.nextClause(),C.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+r.type+"'";throw new C.QueryParseError(n,r.start,r.end)}else e.nextClause()}},C.QueryParser.parseEditDistance=function(e){var t=e.consumeLexeme();if(null!=t){var r=parseInt(t.str,10);if(isNaN(r)){var n="edit distance must be numeric";throw new C.QueryParseError(n,t.start,t.end)}e.currentClause.editDistance=r;var i=e.peekLexeme();if(null!=i)switch(i.type){case C.QueryLexer.TERM:return e.nextClause(),C.QueryParser.parseTerm;case C.QueryLexer.FIELD:return e.nextClause(),C.QueryParser.parseField;case C.QueryLexer.EDIT_DISTANCE:return C.QueryParser.parseEditDistance;case C.QueryLexer.BOOST:return C.QueryParser.parseBoost;case C.QueryLexer.PRESENCE:return e.nextClause(),C.QueryParser.parsePresence;default:n="Unexpected lexeme type '"+i.type+"'";throw new C.QueryParseError(n,i.start,i.end)}else e.nextClause()}},C.QueryParser.parseBoost=function(e){var t=e.consumeLexeme();if(null!=t){var r=parseInt(t.str,10);if(isNaN(r)){var n="boost must be numeric";throw new C.QueryParseError(n,t.start,t.end)}e.currentClause.boost=r;var i=e.peekLexeme();if(null!=i)switch(i.type){case C.QueryLexer.TERM:return e.nextClause(),C.QueryParser.parseTerm;case C.QueryLexer.FIELD:return e.nextClause(),C.QueryParser.parseField;case C.QueryLexer.EDIT_DISTANCE:return C.QueryParser.parseEditDistance;case C.QueryLexer.BOOST:return C.QueryParser.parseBoost;case C.QueryLexer.PRESENCE:return e.nextClause(),C.QueryParser.parsePresence;default:n="Unexpected lexeme type '"+i.type+"'";throw new C.QueryParseError(n,i.start,i.end)}else e.nextClause()}},void 0===(i="function"==typeof(n=function(){return C})?n.call(t,r,t,e):n)||(e.exports=i)}()},function(e,t,r){},function(e,t,r){"use strict";r.r(t);var n=[];function i(e,t){n.push({selector:t,constructor:e})}var s,o,a=function(){function e(){this.createComponents(document.body)}return e.prototype.createComponents=function(e){n.forEach((function(t){e.querySelectorAll(t.selector).forEach((function(e){e.dataset.hasInstance||(new t.constructor({el:e}),e.dataset.hasInstance=String(!0))}))}))},e}(),u=function(e){this.el=e.el},l=r(0),c=(s=function(e,t){return(s=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}s(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)});!function(e){e[e.Idle=0]="Idle",e[e.Loading=1]="Loading",e[e.Ready=2]="Ready",e[e.Failure=3]="Failure"}(o||(o={}));var h=function(e){function t(t){var r=e.call(this,t)||this;r.query="",r.loadingState=o.Idle,r.hasFocus=!1,r.preventPress=!1,r.data=null,r.index=null,r.resultClicked=!1;var n=document.querySelector("#tsd-search-field"),i=document.querySelector(".results");if(!n||!i)throw new Error("The input field or the result list wrapper are not found");return r.field=n,r.results=i,r.base=r.el.dataset.base+"/",r.bindEvents(),r}return c(t,e),t.prototype.loadIndex=function(){var e=this;if(this.loadingState==o.Idle&&!this.data){setTimeout((function(){e.loadingState==o.Idle&&e.setLoadingState(o.Loading)}),500);var t=this.el.dataset.index;t?fetch(t).then((function(e){if(!e.ok)throw new Error("The search index is missing");return e.json()})).then((function(t){e.data=t,e.index=l.Index.load(t.index),e.setLoadingState(o.Ready)})).catch((function(t){console.error(t),e.setLoadingState(o.Failure)})):this.setLoadingState(o.Failure)}},t.prototype.updateResults=function(){if(this.loadingState==o.Ready&&(this.results.textContent="",this.query&&this.index&&this.data)){var e=this.index.search("*"+this.query+"*");0===e.length&&(e=this.index.search("*"+this.query+"~1*"));for(var t=0,r=Math.min(10,e.length);t"+e+""})),s=n.parent||"";(s=s.replace(new RegExp(this.query,"i"),(function(e){return""+e+""})))&&(i=''+s+"."+i);var a=document.createElement("li");a.classList.value=n.classes,a.innerHTML='\n '+i+"\n ",this.results.appendChild(a)}}},t.prototype.setLoadingState=function(e){this.loadingState!=e&&(this.el.classList.remove(o[this.loadingState].toLowerCase()),this.loadingState=e,this.el.classList.add(o[this.loadingState].toLowerCase()),this.updateResults())},t.prototype.setHasFocus=function(e){this.hasFocus!=e&&(this.hasFocus=e,this.el.classList.toggle("has-focus"),e?(this.setQuery(""),this.field.value=""):this.field.value=this.query)},t.prototype.setQuery=function(e){this.query=e.trim(),this.updateResults()},t.prototype.setCurrentResult=function(e){var t=this.results.querySelector(".current");if(t){var r=1==e?t.nextElementSibling:t.previousElementSibling;r&&(t.classList.remove("current"),r.classList.add("current"))}else(t=this.results.querySelector(1==e?"li:first-child":"li:last-child"))&&t.classList.add("current")},t.prototype.gotoCurrentResult=function(){var e=this.results.querySelector(".current");if(e||(e=this.results.querySelector("li:first-child")),e){var t=e.querySelector("a");t&&(window.location.href=t.href),this.field.blur()}},t.prototype.bindEvents=function(){var e=this;this.results.addEventListener("mousedown",(function(){e.resultClicked=!0})),this.results.addEventListener("mouseup",(function(){e.resultClicked=!1,e.setHasFocus(!1)})),this.field.addEventListener("focusin",(function(){e.setHasFocus(!0),e.loadIndex()})),this.field.addEventListener("focusout",(function(){e.resultClicked?e.resultClicked=!1:setTimeout((function(){return e.setHasFocus(!1)}),100)})),this.field.addEventListener("input",(function(){e.setQuery(e.field.value)})),this.field.addEventListener("keydown",(function(t){13==t.keyCode||27==t.keyCode||38==t.keyCode||40==t.keyCode?(e.preventPress=!0,t.preventDefault(),13==t.keyCode?e.gotoCurrentResult():27==t.keyCode?e.field.blur():38==t.keyCode?e.setCurrentResult(-1):40==t.keyCode&&e.setCurrentResult(1)):e.preventPress=!1})),this.field.addEventListener("keypress",(function(t){e.preventPress&&t.preventDefault()})),document.body.addEventListener("keydown",(function(t){t.altKey||t.ctrlKey||t.metaKey||!e.hasFocus&&t.keyCode>47&&t.keyCode<112&&e.field.focus()}))},t}(u),d=function(){function e(){this.listeners={}}return e.prototype.addEventListener=function(e,t){e in this.listeners||(this.listeners[e]=[]),this.listeners[e].push(t)},e.prototype.removeEventListener=function(e,t){if(e in this.listeners)for(var r=this.listeners[e],n=0,i=r.length;n=this.scrollTop||0===this.scrollTop,e!==this.showToolbar&&(this.toolbar.classList.toggle("tsd-page-toolbar--hide"),this.secondaryNav.classList.toggle("tsd-navigation--toolbar-hide")),this.lastY=this.scrollTop},t.instance=new t,t}(d),m=function(){var e=function(t,r){return(e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(t,r)};return function(t,r){function n(){this.constructor=t}e(t,r),t.prototype=null===r?Object.create(r):(n.prototype=r.prototype,new n)}}(),v=function(e){function t(t){var r=e.call(this,t)||this;return r.anchors=[],r.index=-1,y.instance.addEventListener("resize",(function(){return r.onResize()})),y.instance.addEventListener("scroll",(function(e){return r.onScroll(e)})),r.createAnchors(),r}return m(t,e),t.prototype.createAnchors=function(){var e=this,t=window.location.href;-1!=t.indexOf("#")&&(t=t.substr(0,t.indexOf("#"))),this.el.querySelectorAll("a").forEach((function(r){var n=r.href;if(-1!=n.indexOf("#")&&n.substr(0,t.length)==t){var i=n.substr(n.indexOf("#")+1),s=document.querySelector("a.tsd-anchor[name="+i+"]"),o=r.parentNode;s&&o&&e.anchors.push({link:o,anchor:s,position:0})}})),this.onResize()},t.prototype.onResize=function(){for(var e,t=0,r=this.anchors.length;t-1&&r[i].position>t;)i-=1;for(;i-1&&this.anchors[this.index].link.classList.remove("focus"),this.index=i,this.index>-1&&this.anchors[this.index].link.classList.add("focus"))},t}(u),g=function(){var e=function(t,r){return(e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(t,r)};return function(t,r){function n(){this.constructor=t}e(t,r),t.prototype=null===r?Object.create(r):(n.prototype=r.prototype,new n)}}(),x=function(){function e(e,t){this.signature=e,this.description=t}return e.prototype.addClass=function(e){return this.signature.classList.add(e),this.description.classList.add(e),this},e.prototype.removeClass=function(e){return this.signature.classList.remove(e),this.description.classList.remove(e),this},e}(),w=function(e){function t(t){var r=e.call(this,t)||this;return r.groups=[],r.index=-1,r.createGroups(),r.container&&(r.el.classList.add("active"),Array.from(r.el.children).forEach((function(e){e.addEventListener("touchstart",(function(e){return r.onClick(e)})),e.addEventListener("click",(function(e){return r.onClick(e)}))})),r.container.classList.add("active"),r.setIndex(0)),r}return g(t,e),t.prototype.setIndex=function(e){if(e<0&&(e=0),e>this.groups.length-1&&(e=this.groups.length-1),this.index!=e){var t=this.groups[e];if(this.index>-1){var r=this.groups[this.index];r.removeClass("current").addClass("fade-out"),t.addClass("current"),t.addClass("fade-in"),y.instance.triggerResize(),setTimeout((function(){r.removeClass("fade-out"),t.removeClass("fade-in")}),300)}else t.addClass("current"),y.instance.triggerResize();this.index=e}},t.prototype.createGroups=function(){var e=this.el.children;if(!(e.length<2)){this.container=this.el.nextElementSibling;var t=this.container.children;this.groups=[];for(var r=0;r10}})),document.addEventListener(b,(function(){Q=!1})),document.addEventListener("click",(function(e){k&&(e.preventDefault(),e.stopImmediatePropagation(),k=!1)}));var T=function(){var e=function(t,r){return(e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(t,r)};return function(t,r){function n(){this.constructor=t}e(t,r),t.prototype=null===r?Object.create(r):(n.prototype=r.prototype,new n)}}(),_=function(e){function t(t){var r=e.call(this,t)||this;return r.className=r.el.dataset.toggle||"",r.el.addEventListener(b,(function(e){return r.onPointerUp(e)})),r.el.addEventListener("click",(function(e){return e.preventDefault()})),document.addEventListener(L,(function(e){return r.onDocumentPointerDown(e)})),document.addEventListener(b,(function(e){return r.onDocumentPointerUp(e)})),r}return T(t,e),t.prototype.setActive=function(e){if(this.active!=e){this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);var t=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(t),setTimeout((function(){return document.documentElement.classList.remove(t)}),500)}},t.prototype.onPointerUp=function(e){O||(this.setActive(!0),e.preventDefault())},t.prototype.onDocumentPointerDown=function(e){if(this.active){if(e.target.closest(".col-menu, .tsd-filter-group"))return;this.setActive(!1)}},t.prototype.onDocumentPointerUp=function(e){var t=this;if(!O&&this.active&&e.target.closest(".col-menu")){var r=e.target.closest("a");if(r){var n=window.location.href;-1!=n.indexOf("#")&&(n=n.substr(0,n.indexOf("#"))),r.href.substr(0,n.length)==n&&setTimeout((function(){return t.setActive(!1)}),250)}}},t}(u),C=function(){var e=function(t,r){return(e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(t,r)};return function(t,r){function n(){this.constructor=t}e(t,r),t.prototype=null===r?Object.create(r):(n.prototype=r.prototype,new n)}}(),R=function(){function e(e,t){this.key=e,this.value=t,this.defaultValue=t,this.initialize(),window.localStorage[this.key]&&this.setValue(this.fromLocalStorage(window.localStorage[this.key]))}return e.prototype.initialize=function(){},e.prototype.setValue=function(e){if(this.value!=e){var t=this.value;this.value=e,window.localStorage[this.key]=this.toLocalStorage(e),this.handleValueChange(t,e)}},e}(),I=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return C(t,e),t.prototype.initialize=function(){var e=this,t=document.querySelector("#tsd-filter-"+this.key);t&&(this.checkbox=t,this.checkbox.addEventListener("change",(function(){e.setValue(e.checkbox.checked)})))},t.prototype.handleValueChange=function(e,t){this.checkbox&&(this.checkbox.checked=this.value,document.documentElement.classList.toggle("toggle-"+this.key,this.value!=this.defaultValue))},t.prototype.fromLocalStorage=function(e){return"true"==e},t.prototype.toLocalStorage=function(e){return e?"true":"false"},t}(R),j=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return C(t,e),t.prototype.initialize=function(){var e=this;document.documentElement.classList.add("toggle-"+this.key+this.value);var t=document.querySelector("#tsd-filter-"+this.key);if(t){this.select=t;var r=function(){e.select.classList.add("active")};this.select.addEventListener(L,r),this.select.addEventListener("mouseover",r),this.select.addEventListener("mouseleave",(function(){e.select.classList.remove("active")})),this.select.querySelectorAll("li").forEach((function(r){r.addEventListener(b,(function(r){t.classList.remove("active"),e.setValue(r.target.dataset.value||"")}))})),document.addEventListener(L,(function(t){e.select.contains(t.target)||e.select.classList.remove("active")}))}},t.prototype.handleValueChange=function(e,t){this.select.querySelectorAll("li.selected").forEach((function(e){e.classList.remove("selected")}));var r=this.select.querySelector('li[data-value="'+t+'"]'),n=this.select.querySelector(".tsd-select-label");r&&n&&(r.classList.add("selected"),n.textContent=r.textContent),document.documentElement.classList.remove("toggle-"+e),document.documentElement.classList.add("toggle-"+t)},t.prototype.fromLocalStorage=function(e){return e},t.prototype.toLocalStorage=function(e){return e},t}(R),F=function(e){function t(t){var r=e.call(this,t)||this;return r.optionVisibility=new j("visibility","private"),r.optionInherited=new I("inherited",!0),r.optionExternals=new I("externals",!0),r.optionOnlyExported=new I("only-exported",!1),r}return C(t,e),t.isSupported=function(){try{return void 0!==window.localStorage}catch(e){return!1}},t}(u);r(1);i(h,"#tsd-search"),i(v,".menu-highlight"),i(w,".tsd-signatures"),i(_,"a[data-toggle]"),F.isSupported()?i(F,"#tsd-filter"):document.documentElement.classList.add("no-filter");var N=new a;Object.defineProperty(window,"app",{value:N})}]); -------------------------------------------------------------------------------- /docs/assets/js/search.json: -------------------------------------------------------------------------------- 1 | {"kinds":{"1":"Module","64":"Function"},"rows":[{"id":0,"kind":1,"name":"\"superoak\"","url":"modules/_superoak_.html","classes":"tsd-kind-module"},{"id":1,"kind":64,"name":"random","url":"modules/_superoak_.html#random","classes":"tsd-kind-function tsd-parent-kind-module tsd-is-private tsd-is-not-exported","parent":"\"superoak\""},{"id":2,"kind":64,"name":"isOakApplication","url":"modules/_superoak_.html#isoakapplication","classes":"tsd-kind-function tsd-parent-kind-module tsd-is-private tsd-is-not-exported","parent":"\"superoak\""},{"id":3,"kind":64,"name":"superoak","url":"modules/_superoak_.html#superoak","classes":"tsd-kind-function tsd-parent-kind-module","parent":"\"superoak\""}],"index":{"version":"2.3.9","fields":["name","parent"],"fieldVectors":[["name/0",[0,0.87]],["parent/0",[]],["name/1",[1,12.04]],["parent/1",[0,0.077]],["name/2",[2,12.04]],["parent/2",[0,0.077]],["name/3",[0,0.87]],["parent/3",[0,0.077]]],"invertedIndex":[["isoakapplication",{"_index":2,"name":{"2":{}},"parent":{}}],["random",{"_index":1,"name":{"1":{}},"parent":{}}],["superoak",{"_index":0,"name":{"0":{},"3":{}},"parent":{"1":{},"2":{},"3":{}}}]],"pipeline":[]}} -------------------------------------------------------------------------------- /docs/globals.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | superoak 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 |
44 |
45 | Menu 46 |
47 |
48 |
49 |
50 |
51 |
52 | 57 |

superoak

58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |

Index

66 |
67 |
68 |
69 |

Modules

70 | 73 |
74 |
75 |
76 |
77 |
78 | 94 |
95 |
96 |
97 |
98 |

Legend

99 |
100 |
    101 |
  • Function
  • 102 |
103 |
104 |
105 |
106 |
107 |

Generated using TypeDoc

108 |
109 |
110 | 111 | 112 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | superoak 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 |
44 |
45 | Menu 46 |
47 |
48 |
49 |
50 |
51 |
52 | 57 |

superoak

58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |

66 | Super Oak standing in the rain at night – stoically facing the dark battle that is software engineering 67 |

SuperOak

68 |

69 |

70 | HTTP assertions for Deno's Oak web framework made easy via SuperDeno. 71 |

72 |

73 | Current version 74 | Current test status 75 | SuperOak docs 76 | PRs are welcome 77 | SuperOak issues 78 | SuperOak stars 79 | SuperOak forks 80 | SuperOak license 81 | SuperOak is maintained 82 |

83 |

84 | SuperOak latest /x/ version 85 | Minimum supported Deno version 86 | SuperOak dependency count 87 | SuperOak dependency outdatedness 88 | SuperOak cached size 89 |

90 |
91 | 92 |

Table of Contents

93 |
94 | 110 | 111 |

About

112 |
113 |

This module aims to provide a high-level abstraction for testing HTTP in Deno's 114 | Oak web framework. This is a wrapper compatibility layer around 115 | SuperDeno to reduce some of the 116 | boilerplate needed to setup Oak integration + functional tests.

117 | 118 |

Installation

119 |
120 |

This is a Deno module available to import direct from this 121 | repo and via the Deno Registry.

122 |

Before importing, download and install Deno.

123 |

You can then import SuperOak straight into your project:

124 |
import { superoak } from "https://deno.land/x/superoak/mod.ts";
125 | 
126 |

SuperOak is also available on nest.land, a 127 | package registry for Deno on the Blockchain.

128 |
129 |

Note: Some examples in this README are using the unversioned form of the import URL. In production you should always use the versioned import form such as https://deno.land/x/superoak@5.0.0/mod.ts.

130 |
131 | 132 |

Example

133 |
134 |

You may pass a url string (for an already running Oak server), or an Oak 135 | Application object to superoak() - when passing an Oak Application, 136 | SuperOak will automatically handle the creation of a server, binding to a free 137 | ephemeral port and closing of the server on a call to .end().

138 |

SuperOak works with any Deno test framework. Here's an example with Deno's 139 | built-in test framework.

140 |
import { Application, Router } from "jsr:@oak/oak@^17.1.4";
141 | import { superoak } from "https://deno.land/x/superoak@5.0.0/mod.ts";
142 | 
143 | const router = new Router();
144 | router.get("/", (ctx) => {
145 |   ctx.response.body = "Hello Deno!";
146 | });
147 | router.post("/user", (ctx) => {
148 |   ctx.response.body = "Post!";
149 | });
150 | 
151 | const app = new Application();
152 | app.use(router.routes());
153 | app.use(router.allowedMethods());
154 | 
155 | // Send simple GET request
156 | Deno.test("it should support the Oak framework", async () => {
157 |   const request = await superoak(app);
158 |   await request.get("/").expect("Hello Deno!");
159 | });
160 | 
161 | // Custom requests can be built with the superagent API
162 | // https://visionmedia.github.io/superagent/#post--put-requests.
163 | Deno.test("it should allow post requests", async () => {
164 |   const request = await superoak(app);
165 |   await request
166 |     .post("/user")
167 |     .set("Content-Type", "application/json")
168 |     .send('{"name":"superoak"}')
169 |     .expect(200);
170 | });
171 | 
172 |

Save the above to a file demo.test.ts and test it using 173 | deno test --allow-net demo.test.ts.

174 |

For further examples, see the 175 | SuperOak examples, 176 | tests 177 | or the 178 | SuperDeno examples for 179 | inspiration.

180 | 181 |

Documentation

182 |
183 | 190 | 191 |

API

192 |
193 |

Please refer to the 194 | SuperDeno API and 195 | SuperAgent API.

196 | 197 |

FAQs

198 |
199 | 200 |

Property 'get' does not exist on type 'Promise<SuperDeno>' error

201 |
202 |

Unlike SuperDeno, superoak() 203 | returns a promise which will need to be awaited before you can call any method 204 | such as .get("/").

205 |
// ✅ works
206 | Deno.test("it will allow you to make assertions if you await it", async () => {
207 |   const request = await superoak(app);
208 |   await request.get("/").expect(200).expect("Hello Deno!");
209 | });
210 | 
211 | // ❌ won't work
212 | Deno.test("it will allow you to make assertions if you await it", async () => {
213 |   const request = superoak(app);
214 |   await request.get("/").expect(200).expect("Hello Deno!"); // Boom 💥 `Property 'get' does not exist on type 'Promise<SuperDeno>'`
215 | });
216 | 
217 | 218 |

Request has been terminated error

219 |
220 |

Unlike SuperDeno, you cannot 221 | re-use SuperOak instances. If you try you will encounter an error similar to 222 | below:

223 |
Error: Request has been terminated
224 | Possible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.
225 |     at Test.Request.crossDomainError
226 |     at XMLHttpRequestSham.xhr.onreadystatechange
227 |     ...
228 | 
229 |

This is because SuperOak instances automatically close the underlying Oak server 230 | once the assertion chain has completed.

231 |

Instead you should make all of your assertions on a single SuperOak instance, or 232 | create a new SuperOak instance for subsequent assertions like below:

233 |
// ✅ works
234 | Deno.test(
235 |   "it will allow you to make multiple assertions on one SuperOak instance",
236 |   async () => {
237 |     const request = await superoak(app);
238 |     await request.get("/").expect(200).expect("Hello Deno!");
239 |   }
240 | );
241 | 
242 | // ✅ works
243 | Deno.test(
244 |   "it will allow you to re-use the Application for another SuperOak instance",
245 |   async () => {
246 |     const request1 = await superoak(app);
247 |     await request1.get("/").expect(200);
248 | 
249 |     const request2 = await superoak(app);
250 |     await request2.get("/").expect("Hello Deno!");
251 |   }
252 | );
253 | 
254 | // ❌ won't work
255 | Deno.test(
256 |   "it will throw an error if try to re-use a SuperOak instance",
257 |   async () => {
258 |     const request = await superoak(app);
259 |     await request.get("/").expect(200);
260 |     await request.get("/").expect("Hello Deno!"); // Boom 💥 `Error: Request has been terminated`
261 |   }
262 | );
263 | 
264 | 265 |

Contributing

266 |
267 |

Contributing guide

268 |
269 | 270 |

License

271 |
272 |

SuperOak is licensed under the MIT License.

273 |

Icon designed and created by 274 | Hannah Morten.

275 |
276 |
277 | 293 |
294 |
295 |
296 |
297 |

Legend

298 |
299 |
    300 |
  • Function
  • 301 |
302 |
303 |
304 |
305 |
306 |

Generated using TypeDoc

307 |
308 |
309 | 310 | 311 | -------------------------------------------------------------------------------- /docs/modules/_superoak_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "superoak" | superoak 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 |
44 |
45 | Menu 46 |
47 |
48 |
49 |
50 |
51 |
52 | 60 |

Module "superoak"

61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |

Index

69 |
70 |
71 |
72 |

Functions

73 | 78 |
79 |
80 |
81 |
82 |
83 |

Functions

84 |
85 | 86 |

Private isOakApplication

87 |
    88 |
  • isOakApplication(thing: any): boolean
  • 89 |
90 |
    91 |
  • 92 | 97 |
    98 |
    99 |

    Duck typing to determine if is Oak application like.

    100 |
    101 |
    102 |

    Parameters

    103 |
      104 |
    • 105 |
      thing: any
      106 |
      107 |
      108 |
    • 109 |
    110 |

    Returns boolean

    111 |
  • 112 |
113 |
114 |
115 | 116 |

Private random

117 |
    118 |
  • random(min: number, max: number): number
  • 119 |
120 |
    121 |
  • 122 | 127 |
    128 |
    129 |

    Generates a random number between min and max

    130 |
    131 |
    132 |

    Parameters

    133 |
      134 |
    • 135 |
      min: number
      136 |
    • 137 |
    • 138 |
      max: number
      139 |
      140 |
      141 |
    • 142 |
    143 |

    Returns number

    144 |
  • 145 |
146 |
147 |
148 | 149 |

superoak

150 |
    151 |
  • superoak(app: string | any, secure?: boolean): Promise<SuperDeno>
  • 152 |
153 |
    154 |
  • 155 | 160 |
    161 |
    162 |

    Takes a url string (for an already running Oak server), or an Oak Application object.

    163 |
    164 |

    When passing a url string, accepts an optional second argument of secure to determine 165 | whether connections should be over HTTPS (true) or HTTP (false).

    166 |

    When passing an Oak Application, SuperOak will automatically handle the creation of a server, binding 167 | to a free ephemeral port and closing of the server on a call to .end().

    168 |
    169 |

    Parameters

    170 |
      171 |
    • 172 |
      app: string | any
      173 |
    • 174 |
    • 175 |
      Optional secure: boolean
      176 |
      177 |
      178 |
    • 179 |
    180 |

    Returns Promise<SuperDeno>

    181 |
  • 182 |
183 |
184 |
185 |
186 | 211 |
212 |
213 |
214 |
215 |

Legend

216 |
217 |
    218 |
  • Function
  • 219 |
220 |
221 |
222 |
223 |
224 |

Generated using TypeDoc

225 |
226 |
227 | 228 | 229 | -------------------------------------------------------------------------------- /egg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "superoak", 3 | "description": "HTTP assertions for Oak made easy via SuperDeno.", 4 | "version": "5.0.0", 5 | "repository": "https://github.com/cmorten/superoak", 6 | "stable": true, 7 | "checkFormat": false, 8 | "checkTests": false, 9 | "checkInstallation": false, 10 | "check": false, 11 | "files": [ 12 | "./mod.ts", 13 | "./deps.ts", 14 | "./version.ts", 15 | "./lock.json", 16 | "./src/**/*", 17 | "./README.md", 18 | "./LICENSE.md", 19 | "./.github/CHANGELOG.md" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This directory contains a series of self-contained examples that you can use as 4 | starting points for your application's tests: 5 | 6 | - [expect-body](./expect-body) - A basic example to test the returned body is as 7 | expected. 8 | - [repeated-assertions](./repeated-assertions) - A example of how to use repeated 9 | assertions on the same Oak Application object. 10 | - [nested-assertions](./nested-assertions) - A example of how to use nested 11 | assertions on the same Oak Application object. 12 | -------------------------------------------------------------------------------- /examples/expect-body/README.md: -------------------------------------------------------------------------------- 1 | # expect-body 2 | 3 | A basic example of how to test the returned body is as expected. 4 | 5 | This directory contains Oak server code in `server.ts`, which will run a basic 6 | server on localhost port 3000, and a test file `server.test.ts` which uses 7 | SuperOak to test that the server's response body matches what is expected. 8 | 9 | ## How to run this example 10 | 11 | To run this example's SuperOak tests: 12 | 13 | ```bash 14 | # If the repo is cloned locally: 15 | deno test --allow-net ./examples/expect-body/server.test.ts 16 | 17 | # Or remotely: 18 | deno test --allow-net https://raw.githubusercontent.com/cmorten/superoak/main/examples/expect-body/server.test.ts 19 | ``` 20 | 21 | You should then see something like this in the console: 22 | 23 | ```console 24 | $ deno test --allow-net ./examples/expect-body/server.test.ts 25 | running 1 tests 26 | test it should support the Oak framework ... Listening at http://localhost:2940 27 | ok (20ms) 28 | 29 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (21ms) 30 | ``` 31 | 32 | --- 33 | 34 | If you just want to run the server: 35 | 36 | ```bash 37 | # If the repo is cloned locally: 38 | deno run --allow-net ./examples/expect-body/server.ts 39 | 40 | # Or remotely: 41 | deno run --allow-net https://raw.githubusercontent.com/cmorten/superoak/main/examples/expect-body/server.ts 42 | ``` 43 | -------------------------------------------------------------------------------- /examples/expect-body/server.test.ts: -------------------------------------------------------------------------------- 1 | import { superoak } from "../../mod.ts"; 2 | import { app } from "./server.ts"; 3 | 4 | Deno.test("it should support the Oak framework", async () => { 5 | const request = await superoak(app); 6 | await request.get("/").expect("Hello Deno!"); 7 | }); 8 | -------------------------------------------------------------------------------- /examples/expect-body/server.ts: -------------------------------------------------------------------------------- 1 | import { Application, Router } from "../../test/deps.ts"; 2 | 3 | const router = new Router(); 4 | router.get("/", (ctx) => { 5 | ctx.response.body = "Hello Deno!"; 6 | }); 7 | 8 | const app = new Application(); 9 | app.use(router.routes()); 10 | app.use(router.allowedMethods()); 11 | 12 | app.addEventListener("listen", ({ port }) => { 13 | console.log(`Listening at http://localhost:${port}`); 14 | }); 15 | 16 | if (import.meta.main) { 17 | await app.listen({ port: 3000 }); 18 | } 19 | 20 | export { app }; 21 | -------------------------------------------------------------------------------- /examples/nested-assertions/README.md: -------------------------------------------------------------------------------- 1 | # nested-assertions 2 | 3 | A example of how to nest assertions on the same Oak Application object. 4 | 5 | This directory contains Oak server code in `server.ts`, which will run a basic 6 | server on localhost port 3000, and a test file `server.test.ts` which uses 7 | SuperOak to test that the server's response body and status code matches what is 8 | expected. 9 | 10 | ## How to run this example 11 | 12 | To run this example's SuperOak tests: 13 | 14 | ```bash 15 | # If the repo is cloned locally: 16 | deno test --allow-net ./examples/nested-assertions/server.test.ts 17 | 18 | # Or remotely: 19 | deno test --allow-net https://raw.githubusercontent.com/cmorten/superoak/main/examples/nested-assertions/server.test.ts 20 | ``` 21 | 22 | You should then see something like this in the console: 23 | 24 | ```console 25 | $ deno test --allow-net ./examples/nested-assertions/server.test.ts 26 | running 2 tests 27 | test it will allow you to make multiple assertions on one SuperOak instance ... ok (16ms) 28 | test it will allow you to re-use the Application for another SuperOak instance ... ok (7ms) 29 | 30 | test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (23ms) 31 | ``` 32 | 33 | --- 34 | 35 | If you just want to run the server: 36 | 37 | ```bash 38 | # If the repo is cloned locally: 39 | deno run --allow-net ./examples/nested-assertions/server.ts 40 | 41 | # Or remotely: 42 | deno run --allow-net https://raw.githubusercontent.com/cmorten/superoak/main/examples/nested-assertions/server.ts 43 | ``` 44 | -------------------------------------------------------------------------------- /examples/nested-assertions/server.test.ts: -------------------------------------------------------------------------------- 1 | import { superoak } from "../../mod.ts"; 2 | import { expect } from "../../test/deps.ts"; 3 | import { app } from "./server.ts"; 4 | 5 | Deno.test( 6 | "it will allow you to make nested assertions", 7 | async () => { 8 | const request1 = await superoak(app); 9 | const request2 = await superoak(app); 10 | 11 | let done: () => void; 12 | const donePromise = new Promise((resolve) => done = resolve); 13 | 14 | request1.get("/").expect(200).end((err, response1) => { 15 | if (err) { 16 | throw err; 17 | } 18 | 19 | request2.get("/hello").expect(200).end((err, response2) => { 20 | if (err) { 21 | throw err; 22 | } 23 | 24 | expect(response1.body).toEqual(response2.body); 25 | done(); 26 | }); 27 | }); 28 | 29 | await donePromise; 30 | }, 31 | ); 32 | 33 | Deno.test( 34 | "it will allow you to make multiple assertions", 35 | async () => { 36 | const request1 = await superoak(app); 37 | const request2 = await superoak(app); 38 | 39 | const response = await request1.get("/") 40 | .expect(200); 41 | 42 | const response2 = await request2 43 | .get("/hello") 44 | .expect(200); 45 | 46 | expect(response.body).toEqual(response2.body); 47 | }, 48 | ); 49 | -------------------------------------------------------------------------------- /examples/nested-assertions/server.ts: -------------------------------------------------------------------------------- 1 | import { Application, Router } from "../../test/deps.ts"; 2 | 3 | const router = new Router(); 4 | const app = new Application(); 5 | 6 | const globalResponse = "Hello Deno!"; 7 | 8 | router.get("/", (ctx) => { 9 | ctx.response.body = globalResponse; 10 | }); 11 | 12 | router.get("/hello", (ctx) => { 13 | ctx.response.body = globalResponse; 14 | }); 15 | 16 | app.use(router.routes()); 17 | app.use(router.allowedMethods()); 18 | 19 | if (import.meta.main) { 20 | await app.listen({ port: 3000 }); 21 | } 22 | 23 | export { app }; 24 | -------------------------------------------------------------------------------- /examples/repeated-assertions/README.md: -------------------------------------------------------------------------------- 1 | # repeated-assertions 2 | 3 | A example of how to repeated assertions on the same Oak Application object. 4 | 5 | This directory contains Oak server code in `server.ts`, which will run a basic 6 | server on localhost port 3000, and a test file `server.test.ts` which uses 7 | SuperOak to test that the server's response body and status code matches what is 8 | expected. 9 | 10 | ## How to run this example 11 | 12 | To run this example's SuperOak tests: 13 | 14 | ```bash 15 | # If the repo is cloned locally: 16 | deno test --allow-net ./examples/repeated-assertions/server.test.ts 17 | 18 | # Or remotely: 19 | deno test --allow-net https://raw.githubusercontent.com/cmorten/superoak/main/examples/repeated-assertions/server.test.ts 20 | ``` 21 | 22 | You should then see something like this in the console: 23 | 24 | ```console 25 | $ deno test --allow-net ./examples/repeated-assertions/server.test.ts 26 | running 2 tests 27 | test it will allow you to make multiple assertions on one SuperOak instance ... ok (16ms) 28 | test it will allow you to re-use the Application for another SuperOak instance ... ok (7ms) 29 | 30 | test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (23ms) 31 | ``` 32 | 33 | --- 34 | 35 | If you just want to run the server: 36 | 37 | ```bash 38 | # If the repo is cloned locally: 39 | deno run --allow-net ./examples/repeated-assertions/server.ts 40 | 41 | # Or remotely: 42 | deno run --allow-net https://raw.githubusercontent.com/cmorten/superoak/main/examples/repeated-assertions/server.ts 43 | ``` 44 | -------------------------------------------------------------------------------- /examples/repeated-assertions/server.test.ts: -------------------------------------------------------------------------------- 1 | import { superoak } from "../../mod.ts"; 2 | import { app } from "./server.ts"; 3 | 4 | Deno.test( 5 | "it will allow you to make multiple assertions on one SuperOak instance", 6 | async () => { 7 | const request = await superoak(app); 8 | 9 | await request.get("/").expect(200).expect("Hello Deno!"); 10 | }, 11 | ); 12 | 13 | Deno.test( 14 | "it will allow you to re-use the Application for another SuperOak instance", 15 | async () => { 16 | let request = await superoak(app); 17 | 18 | await request.get("/") 19 | .expect(200); 20 | 21 | request = await superoak(app); 22 | 23 | await request 24 | .get("/") 25 | .expect("Hello Deno!"); 26 | }, 27 | ); 28 | -------------------------------------------------------------------------------- /examples/repeated-assertions/server.ts: -------------------------------------------------------------------------------- 1 | import { Application, Router } from "../../test/deps.ts"; 2 | 3 | const router = new Router(); 4 | const app = new Application(); 5 | 6 | router.get("/", (ctx) => { 7 | ctx.response.body = "Hello Deno!"; 8 | }); 9 | 10 | app.use(router.routes()); 11 | app.use(router.allowedMethods()); 12 | 13 | if (import.meta.main) { 14 | await app.listen({ port: 3000 }); 15 | } 16 | 17 | export { app }; 18 | -------------------------------------------------------------------------------- /lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "specifiers": { 4 | "jsr:@std/assert@^1.0.13": "1.0.13", 5 | "jsr:@std/cli@^1.0.17": "1.0.17", 6 | "jsr:@std/encoding@^1.0.10": "1.0.10", 7 | "jsr:@std/fmt@^1.0.8": "1.0.8", 8 | "jsr:@std/html@^1.0.4": "1.0.4", 9 | "jsr:@std/http@^1.0.16": "1.0.16", 10 | "jsr:@std/internal@^1.0.6": "1.0.7", 11 | "jsr:@std/media-types@^1.1.0": "1.1.0", 12 | "jsr:@std/net@^1.0.4": "1.0.4", 13 | "jsr:@std/path@^1.0.9": "1.0.9", 14 | "jsr:@std/streams@^1.0.9": "1.0.9" 15 | }, 16 | "jsr": { 17 | "@std/assert@1.0.13": { 18 | "integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29", 19 | "dependencies": [ 20 | "jsr:@std/internal" 21 | ] 22 | }, 23 | "@std/cli@1.0.17": { 24 | "integrity": "e15b9abe629e17be90cc6216327f03a29eae613365f1353837fa749aad29ce7b" 25 | }, 26 | "@std/encoding@1.0.10": { 27 | "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" 28 | }, 29 | "@std/fmt@1.0.8": { 30 | "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7" 31 | }, 32 | "@std/html@1.0.4": { 33 | "integrity": "eff3497c08164e6ada49b7f81a28b5108087033823153d065e3f89467dd3d50e" 34 | }, 35 | "@std/http@1.0.16": { 36 | "integrity": "80c8d08c4bfcf615b89978dcefb84f7e880087cf3b6b901703936f3592a06933", 37 | "dependencies": [ 38 | "jsr:@std/cli", 39 | "jsr:@std/encoding", 40 | "jsr:@std/fmt", 41 | "jsr:@std/html", 42 | "jsr:@std/media-types", 43 | "jsr:@std/net", 44 | "jsr:@std/path", 45 | "jsr:@std/streams" 46 | ] 47 | }, 48 | "@std/internal@1.0.7": { 49 | "integrity": "39eeb5265190a7bc5d5591c9ff019490bd1f2c3907c044a11b0d545796158a0f" 50 | }, 51 | "@std/media-types@1.1.0": { 52 | "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" 53 | }, 54 | "@std/net@1.0.4": { 55 | "integrity": "2f403b455ebbccf83d8a027d29c5a9e3a2452fea39bb2da7f2c04af09c8bc852" 56 | }, 57 | "@std/path@1.0.9": { 58 | "integrity": "260a49f11edd3db93dd38350bf9cd1b4d1366afa98e81b86167b4e3dd750129e" 59 | }, 60 | "@std/streams@1.0.9": { 61 | "integrity": "a9d26b1988cdd7aa7b1f4b51e1c36c1557f3f252880fa6cc5b9f37078b1a5035" 62 | } 63 | }, 64 | "remote": { 65 | "https://deno.land/std@0.213.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", 66 | "https://deno.land/std@0.213.0/assert/_diff.ts": "dcc63d94ca289aec80644030cf88ccbf7acaa6fbd7b0f22add93616b36593840", 67 | "https://deno.land/std@0.213.0/assert/_format.ts": "0ba808961bf678437fb486b56405b6fefad2cf87b5809667c781ddee8c32aff4", 68 | "https://deno.land/std@0.213.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", 69 | "https://deno.land/std@0.213.0/assert/assert_almost_equals.ts": "8b96b7385cc117668b0720115eb6ee73d04c9bcb2f5d2344d674918c9113688f", 70 | "https://deno.land/std@0.213.0/assert/assert_array_includes.ts": "1688d76317fd45b7e93ef9e2765f112fdf2b7c9821016cdfb380b9445374aed1", 71 | "https://deno.land/std@0.213.0/assert/assert_equals.ts": "4497c56fe7d2993b0d447926702802fc0becb44e319079e8eca39b482ee01b4e", 72 | "https://deno.land/std@0.213.0/assert/assert_exists.ts": "24a7bf965e634f909242cd09fbaf38bde6b791128ece08e33ab08586a7cc55c9", 73 | "https://deno.land/std@0.213.0/assert/assert_false.ts": "6f382568e5128c0f855e5f7dbda8624c1ed9af4fcc33ef4a9afeeedcdce99769", 74 | "https://deno.land/std@0.213.0/assert/assert_greater.ts": "4945cf5729f1a38874d7e589e0fe5cc5cd5abe5573ca2ddca9d3791aa891856c", 75 | "https://deno.land/std@0.213.0/assert/assert_greater_or_equal.ts": "573ed8823283b8d94b7443eb69a849a3c369a8eb9666b2d1db50c33763a5d219", 76 | "https://deno.land/std@0.213.0/assert/assert_instance_of.ts": "72dc1faff1e248692d873c89382fa1579dd7b53b56d52f37f9874a75b11ba444", 77 | "https://deno.land/std@0.213.0/assert/assert_is_error.ts": "6596f2b5ba89ba2fe9b074f75e9318cda97a2381e59d476812e30077fbdb6ed2", 78 | "https://deno.land/std@0.213.0/assert/assert_less.ts": "2b4b3fe7910f65f7be52212f19c3977ecb8ba5b2d6d0a296c83cde42920bb005", 79 | "https://deno.land/std@0.213.0/assert/assert_less_or_equal.ts": "b93d212fe669fbde959e35b3437ac9a4468f2e6b77377e7b6ea2cfdd825d38a0", 80 | "https://deno.land/std@0.213.0/assert/assert_match.ts": "ec2d9680ed3e7b9746ec57ec923a17eef6d476202f339ad91d22277d7f1d16e1", 81 | "https://deno.land/std@0.213.0/assert/assert_not_equals.ts": "f3edda73043bc2c9fae6cbfaa957d5c69bbe76f5291a5b0466ed132c8789df4c", 82 | "https://deno.land/std@0.213.0/assert/assert_not_instance_of.ts": "8f720d92d83775c40b2542a8d76c60c2d4aeddaf8713c8d11df8984af2604931", 83 | "https://deno.land/std@0.213.0/assert/assert_not_match.ts": "b4b7c77f146963e2b673c1ce4846473703409eb93f5ab0eb60f6e6f8aeffe39f", 84 | "https://deno.land/std@0.213.0/assert/assert_not_strict_equals.ts": "da0b8ab60a45d5a9371088378e5313f624799470c3b54c76e8b8abeec40a77be", 85 | "https://deno.land/std@0.213.0/assert/assert_object_match.ts": "e85e5eef62a56ce364c3afdd27978ccab979288a3e772e6855c270a7b118fa49", 86 | "https://deno.land/std@0.213.0/assert/assert_rejects.ts": "e9e0c8d9c3e164c7ac962c37b3be50577c5a2010db107ed272c4c1afb1269f54", 87 | "https://deno.land/std@0.213.0/assert/assert_strict_equals.ts": "0425a98f70badccb151644c902384c12771a93e65f8ff610244b8147b03a2366", 88 | "https://deno.land/std@0.213.0/assert/assert_string_includes.ts": "dfb072a890167146f8e5bdd6fde887ce4657098e9f71f12716ef37f35fb6f4a7", 89 | "https://deno.land/std@0.213.0/assert/assert_throws.ts": "edddd86b39606c342164b49ad88dd39a26e72a26655e07545d172f164b617fa7", 90 | "https://deno.land/std@0.213.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", 91 | "https://deno.land/std@0.213.0/assert/equal.ts": "fae5e8a52a11d3ac694bbe1a53e13a7969e3f60791262312e91a3e741ae519e2", 92 | "https://deno.land/std@0.213.0/assert/fail.ts": "f310e51992bac8e54f5fd8e44d098638434b2edb802383690e0d7a9be1979f1c", 93 | "https://deno.land/std@0.213.0/assert/mod.ts": "325df8c0683ad83a873b9691aa66b812d6275fc9fec0b2d180ac68a2c5efed3b", 94 | "https://deno.land/std@0.213.0/assert/unimplemented.ts": "47ca67d1c6dc53abd0bd729b71a31e0825fc452dbcd4fde4ca06789d5644e7fd", 95 | "https://deno.land/std@0.213.0/assert/unreachable.ts": "38cfecb95d8b06906022d2f9474794fca4161a994f83354fd079cac9032b5145", 96 | "https://deno.land/std@0.213.0/async/delay.ts": "8e1d18fe8b28ff95885e2bc54eccec1713f57f756053576d8228e6ca110793ad", 97 | "https://deno.land/std@0.213.0/fmt/colors.ts": "aeaee795471b56fc62a3cb2e174ed33e91551b535f44677f6320336aabb54fbb", 98 | "https://deno.land/std@0.213.0/http/server.ts": "6dce295abc169d0956ae00432441331b3425afad4d79e8b3475739be2f04d614", 99 | "https://deno.land/std@0.213.0/http/status.ts": "ed61b4882af2514a81aefd3245e8df4c47b9a8e54929a903577643d2d1ebf514", 100 | "https://deno.land/x/free_port@v1.2.0/mod.ts": "512646732aaea41fbfd1f210f3ae82660f38251777d189d290da331d0235a58e", 101 | "https://deno.land/x/opine@2.3.4/src/methods.ts": "0481daecc6068d24e9e5391818baddf555ab803d39a465dcd259161f8bd8ee49", 102 | "https://deno.land/x/opine@2.3.4/src/utils/mergeDescriptors.ts": "1fe498d4a1a8dcfd3570f9ca5e0647590d86d029b3c340bfcfdb57002851e41b", 103 | "https://deno.land/x/superdeno@4.9.0/deps.ts": "acb88a5969aae0bcc82e053cb433cd183a10cc656495caa634b6e22a79156c4e", 104 | "https://deno.land/x/superdeno@4.9.0/mod.ts": "fa91c501867a4302a4bc92d63cbf934fe5475ebb7bf58335338e001147263c87", 105 | "https://deno.land/x/superdeno@4.9.0/src/close.ts": "8bd4ab602ebbb048d06697d0c48c30be5f78ab9ad673850965e8014d78cca7a8", 106 | "https://deno.land/x/superdeno@4.9.0/src/superagent.ts": "8f60187f9278b154ef6bccf09a5ff7d45f81103ad0ce02d45518a6bbe63ce764", 107 | "https://deno.land/x/superdeno@4.9.0/src/superdeno.ts": "2e2cd4898961ac7688f0c2a4b210bf560a338f6601bd231d74bf8a0956880311", 108 | "https://deno.land/x/superdeno@4.9.0/src/test.ts": "1ab3c8c98160af8c3b30e097809d5c57bdd38d7b42c703f3f170f8452ad06c0f", 109 | "https://deno.land/x/superdeno@4.9.0/src/utils.ts": "09a2e65cc5cc2a261b885f0e66ee84e96e978181975a0728636d20e48b67bd89", 110 | "https://deno.land/x/superdeno@4.9.0/src/xhrSham.js": "6a35aed77bbe98324fe3b4d7430463b7cd6d3b43445ffdccd1fc327dc59dd3c6", 111 | "https://deno.land/x/superdeno@4.9.0/version.ts": "4f8ba8f2a6b201e8e96818d3ab5c43aef1db751523c4b79160500664b72f87de", 112 | "https://deno.land/x/superdeno@5.0.1/deps.ts": "57b47f41ed8b17df087ea385b74921e1b758bccaf31d0d55afaeeff24cd87e28", 113 | "https://deno.land/x/superdeno@5.0.1/mod.ts": "fa91c501867a4302a4bc92d63cbf934fe5475ebb7bf58335338e001147263c87", 114 | "https://deno.land/x/superdeno@5.0.1/src/close.ts": "8bd4ab602ebbb048d06697d0c48c30be5f78ab9ad673850965e8014d78cca7a8", 115 | "https://deno.land/x/superdeno@5.0.1/src/superagent.ts": "8f60187f9278b154ef6bccf09a5ff7d45f81103ad0ce02d45518a6bbe63ce764", 116 | "https://deno.land/x/superdeno@5.0.1/src/superdeno.ts": "53de0697e44658a8affa920edfaf835dbaecfa7ec446d5b5ae790f1f989f55d8", 117 | "https://deno.land/x/superdeno@5.0.1/src/test.ts": "aba9c5428cf31d0d63e4beb3ef53df346ad42a8dd432a1d18976b326ec2804c3", 118 | "https://deno.land/x/superdeno@5.0.1/src/utils.ts": "f90f54ba43f87f3bb104036a2b6a1b0189327fc86ece5d4e21a445f072e09482", 119 | "https://deno.land/x/superdeno@5.0.1/src/xhrSham.js": "6a35aed77bbe98324fe3b4d7430463b7cd6d3b43445ffdccd1fc327dc59dd3c6", 120 | "https://deno.land/x/superdeno@5.0.1/version.ts": "8ea56025ee2d327922bd8fa58692ea7dbb4069003c2325929bb56216d8257eb5", 121 | "https://jspm.dev/npm:call-bind@1.0.5!cjs": "09f8399c727fc1e9d58fdafc0a729b45bf37b7ee0c11d9d0b39abe37ac42ccf5", 122 | "https://jspm.dev/npm:call-bind@1.0.5/callBound!cjs": "55fa05e2b115eeaef9ff684e3df12de253e6644a40ad09b5722f3a9a8df8f645", 123 | "https://jspm.dev/npm:call-bind@1/callBound!cjs": "9cf2ef160025d392767618c2f0cb72d32cf14caa3fbeb493c6df9bde9d7fca8d", 124 | "https://jspm.dev/npm:component-emitter@1!cjs": "26c2994a5fcac1cd9156b00be96c5e2f006dd76338095a96006ac3a47c6c327d", 125 | "https://jspm.dev/npm:component-emitter@1.3.0!cjs": "757cafefb0bf5639f3f90b2267a7d168e03631e731c2a79fca847b735695e196", 126 | "https://jspm.dev/npm:define-data-property@1!cjs": "37b65cb06c826730306a5f766de69da37b96076c96ea11a47667e9429623f937", 127 | "https://jspm.dev/npm:define-data-property@1.1.1!cjs": "4ac6fa4b9d7ba7ccc83ffa350c58112ee878a450a97375217f66508d5673c822", 128 | "https://jspm.dev/npm:fast-safe-stringify@2!cjs": "d8dd0803af23f037ffb44c13e18333131af27ebe582f55fd498b6e3c8f6d5a9d", 129 | "https://jspm.dev/npm:fast-safe-stringify@2.1.1!cjs": "8a14a2de8a07a719c74aa63ffa5ff635fc55e9ee5d5a79fbc2e087dc4aa1940e", 130 | "https://jspm.dev/npm:function-bind@1!cjs": "73fbc50bf85e8a6ca150609e98c396301c1ae5a1603e50ce8c64e95f646e0ce0", 131 | "https://jspm.dev/npm:function-bind@1.1.2!cjs": "bbb663bc4e50f400a8ca0de9e0bfdaaa7022695f86b2806a48dc1afc5b4195a8", 132 | "https://jspm.dev/npm:function-bind@1.1.2/implementation!cjs": "ebdc0ec85854db19d7e21081b368891394f86e21c6d786273c327762cb46ea6a", 133 | "https://jspm.dev/npm:get-intrinsic@1!cjs": "f6d9266edc586632e8f6d8d6c5ca28fb2c0d5ee9c9d9252df9aafd57eda9fcea", 134 | "https://jspm.dev/npm:get-intrinsic@1.2.2!cjs": "723fcebc493a45d5af8ecb366020a6cc2ce9bd4759bad699c1172015cb193f65", 135 | "https://jspm.dev/npm:gopd@1!cjs": "c220469947b77de2c5e4b115beda16397bf6133c5b873b8e24e85b902ee6dc82", 136 | "https://jspm.dev/npm:gopd@1.0.1!cjs": "b38da4f4b49cfef31e3aa8d62fdd136cf0fe99a5df6c603a426f97248f3cf4ab", 137 | "https://jspm.dev/npm:has-property-descriptors@1!cjs": "b1a828f75a22a5614b136dd3da1be98cc744a2cd6bfed9bd8c338a8d51a570d1", 138 | "https://jspm.dev/npm:has-property-descriptors@1.0.1!cjs": "f8da64823507f597f3cb44a2f3576e350df72e1033ef5e7a5b30d771e81c0819", 139 | "https://jspm.dev/npm:has-proto@1!cjs": "78a2914e5525d531426c5d69fd5aa23671ec359c6c527b9791327f60ad1b6682", 140 | "https://jspm.dev/npm:has-proto@1.0.1!cjs": "0a9d605f1d310f859265780011d6343a7869cadf3a9e02fd6cc949c2924b528c", 141 | "https://jspm.dev/npm:has-symbols@1!cjs": "48faf647d225b64fa235ccc3e5a848e72221b0230935e421066a5de39aa89c3a", 142 | "https://jspm.dev/npm:has-symbols@1.0.3!cjs": "36965f84e4e0ea1abeddb6928d0719a2648e61ceb9825df185b40d05cddb64df", 143 | "https://jspm.dev/npm:has-symbols@1.0.3/shams!cjs": "669673e1dc7691c0b397580760121d57f3a5c5101dd70be2e8dd7d2a044de2e9", 144 | "https://jspm.dev/npm:hasown@2!cjs": "9a39af846b167cae93b7a40f1ba4c97255bb5b07a1481da853a29bb68d24e603", 145 | "https://jspm.dev/npm:hasown@2.0.0!cjs": "f52fd2477e345530f759465a984023f23d8261c4a54970e619daf1da6a2e85f5", 146 | "https://jspm.dev/npm:object-inspect@1!cjs": "dc197b471ed55ecf2eabeb8da9aaee277e97831e65192531432a4ec2346211d9", 147 | "https://jspm.dev/npm:object-inspect@1.13.1!cjs": "cec116e5c2b7d6b75e178d2541d70475d716ad912e3d5599e5c2d97284a9cb3e", 148 | "https://jspm.dev/npm:qs@6!cjs": "210de1e090ac836c2495c19dfea88fc74b49de1b308241f8c9490d27ab6e0195", 149 | "https://jspm.dev/npm:qs@6.11.2!cjs": "5da52fff60f7b1a6b1c73cdea2d9fc5d5588fa6c551b2a0ea2a1ebbb2a5e559f", 150 | "https://jspm.dev/npm:qs@6.11.2/_/e71c21de.js": "cfe49eb949fb7291803f1ed2f4c0a244b8fca3b6936f5082fc97581a0663e427", 151 | "https://jspm.dev/npm:qs@6.11.2/lib/stringify!cjs": "35d39c5871af151efe9ccca8e4ebecbf0282f97287b5fad56ebac369f69c2581", 152 | "https://jspm.dev/npm:set-function-length@1!cjs": "b4c766d874ba261ff0c11aa18a6bf4510ecf8da09a7219da83a62772e0bc1b41", 153 | "https://jspm.dev/npm:set-function-length@1.1.1!cjs": "f52607660d1f50e19e645ab49e6a4adf27fa4ae909867ec9950e993c430e4ca1", 154 | "https://jspm.dev/npm:side-channel@1!cjs": "a07dfe7165af0d7f916d089490c38839397abcd8b218e4566b270858c9a0ea04", 155 | "https://jspm.dev/npm:side-channel@1.0.4!cjs": "db65b31b6f9e67d57f04e26d71eb5b376306f5a89ab46fae1278c3ffefb19663", 156 | "https://jspm.dev/npm:superagent@6.1.0!cjs": "fcf1c0b17cb3ff899b59ae178fc4ab74ad3b592d7fa8b44b16394001758e3176", 157 | "https://jspm.dev/npm:superagent@6.1.0/lib/agent-base!cjs": "cfe465965a55d80114d835143717413945d0bbc46355d0f7f8200a89902ed006", 158 | "https://jspm.dev/npm:superagent@6.1.0/lib/is-object!cjs": "95f67ff49b42fd5e82114b9d54a4b3fe1ac98813aed7ceaf53d314983f59820a", 159 | "https://jspm.dev/npm:superagent@6.1.0/lib/request-base!cjs": "e361c341aa75d7417c918bc8fb697d0ccf96101e039dd2f00e5e45c01c534caa", 160 | "https://jspm.dev/npm:superagent@6.1.0/lib/response-base!cjs": "00ac549f34d73c2753caa798aa7eb781051179013e3418ff0868a1e1904a8913", 161 | "https://jspm.dev/npm:superagent@6.1.0/lib/utils!cjs": "ea706523553983c96ef4ab2f191c61c53fb8b78ad8ff2472b48f1385e896c030", 162 | "https://jspm.dev/superagent@6.1.0": "4b3082d71252c42abd3930d85d1f3c4b2e937e0fab2b5f1c9d19eac20dea89a9" 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export { superoak } from "./src/superoak.ts"; 2 | export { DENO_SUPPORTED_VERSIONS, VERSION } from "./version.ts"; 3 | 4 | /** 5 | * Re-export useful types from SuperDeno. 6 | */ 7 | export type { IRequest, IResponse, SuperDeno, Test } from "./deps.ts"; 8 | -------------------------------------------------------------------------------- /src/superoak.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | 3 | import { getFreePort, SuperDeno, superdeno } from "../deps.ts"; 4 | 5 | /** 6 | * Generates a random number between min and max 7 | * @param {number} min 8 | * @param {number} max 9 | * 10 | * @returns {number} 11 | * @private 12 | */ 13 | function random(min: number, max: number): number { 14 | return Math.round(Math.random() * (max - min)) + min; 15 | } 16 | 17 | // TODO: this should be handled with better type logic as is brittle to 18 | // Oak API interface changes. 19 | /** 20 | * Duck typing to determine if is Oak application like. 21 | * 22 | * @param {any} thing 23 | * 24 | * @returns {boolean} 25 | * @private 26 | */ 27 | function isOakApplication(thing: any): boolean { 28 | return typeof thing === "object" && typeof thing?.listen === "function" && 29 | typeof thing?.addEventListener === "function"; 30 | } 31 | 32 | /** 33 | * Takes a url string (for an already running Oak server), or an Oak `Application` object. 34 | * 35 | * When passing a url string, accepts an optional second argument of `secure` to determine 36 | * whether connections should be over _HTTPS_ (`true`) or _HTTP_ (`false`). 37 | * 38 | * When passing an Oak `Application`, SuperOak will automatically handle the creation of a server, binding 39 | * to a free ephemeral port and closing of the server on a call to `.end()`. 40 | * 41 | * @param {string|Application} app 42 | * @param {?boolean} secure 43 | * 44 | * @returns {Promise} 45 | * @public 46 | */ 47 | export async function superoak( 48 | app: string | any, 49 | secure?: boolean, 50 | ): Promise { 51 | if (isOakApplication(app)) { 52 | const controller = new AbortController(); 53 | const freePort = await getFreePort(random(1024, 49151)); 54 | 55 | let listenPromise: Promise; 56 | let closePromise: Promise; 57 | 58 | return new Promise((resolve) => { 59 | app.addEventListener( 60 | "listen", 61 | ( 62 | { hostname, port, secure }: { 63 | hostname: string; 64 | port: number; 65 | secure: boolean; 66 | }, 67 | ) => { 68 | const serverSham = { 69 | async listenAndServe() {}, 70 | async close() { 71 | controller.abort(); 72 | 73 | if (closePromise) { 74 | await closePromise; 75 | } 76 | 77 | if (listenPromise) { 78 | await listenPromise; 79 | } 80 | }, 81 | get addrs() { 82 | return [{ 83 | port, 84 | hostname: hostname as string, 85 | transport: "tcp" as const, 86 | }]; 87 | }, 88 | }; 89 | 90 | resolve(superdeno(serverSham, secure)); 91 | }, 92 | ); 93 | 94 | closePromise = new Promise( 95 | (resolve) => { 96 | app.addEventListener("close", () => resolve()); 97 | }, 98 | ); 99 | 100 | listenPromise = app.listen( 101 | { hostname: "127.0.0.1", port: freePort, signal: controller.signal }, 102 | ); 103 | }); 104 | } 105 | 106 | return superdeno(app, secure); 107 | } 108 | -------------------------------------------------------------------------------- /test/deps.ts: -------------------------------------------------------------------------------- 1 | export { dirname, join } from "jsr:@std/path@^1.0.9"; 2 | export { expect } from "https://deno.land/x/expect@v0.4.2/mod.ts"; 3 | export { isFreePort } from "https://deno.land/x/free_port@v1.2.0/mod.ts"; 4 | 5 | export * from "jsr:@oak/oak@^17.1.4"; 6 | -------------------------------------------------------------------------------- /test/superoak.test.ts: -------------------------------------------------------------------------------- 1 | import { Application, expect, isFreePort, Router } from "./deps.ts"; 2 | import { getFreePort, Test } from "../deps.ts"; 3 | import { describe, it } from "./utils.ts"; 4 | import { superoak } from "../mod.ts"; 5 | 6 | describe("superoak(url)", () => { 7 | it("superoak(url): should support `superoak(url)` if consumer wants to handle server themselves", async (done) => { 8 | const router = new Router(); 9 | const app = new Application(); 10 | 11 | router.get("/", (ctx) => { 12 | ctx.response.body = "hello"; 13 | }); 14 | 15 | app.use(router.routes()); 16 | app.use(router.allowedMethods()); 17 | 18 | const controller = new AbortController(); 19 | const { signal } = controller; 20 | 21 | app.addEventListener("listen", async ({ hostname, port, secure }) => { 22 | const protocol = secure ? "https" : "http"; 23 | const url = `${protocol}://${hostname}:${port}`; 24 | 25 | (await superoak(url)) 26 | .get("/") 27 | .expect("hello", () => { 28 | controller.abort(); 29 | done(); 30 | }); 31 | }); 32 | 33 | app.listen( 34 | { hostname: "localhost", port: await getFreePort(1024), signal }, 35 | ); 36 | }); 37 | 38 | describe(".end(cb)", () => { 39 | it("Oak: superoak(url): .end(cb): should set `this` to the test object when calling the `cb` in `.end(cb)`", async (done) => { 40 | const router = new Router(); 41 | const app = new Application(); 42 | 43 | router.get("/", (ctx) => { 44 | ctx.response.body = "hello"; 45 | }); 46 | 47 | app.use(router.routes()); 48 | app.use(router.allowedMethods()); 49 | 50 | const controller = new AbortController(); 51 | const { signal } = controller; 52 | 53 | app.addEventListener( 54 | "listen", 55 | async ({ hostname, port, secure }) => { 56 | const protocol = secure ? "https" : "http"; 57 | const url = `${protocol}://${hostname}:${port}`; 58 | const test = (await superoak(url)).get("/"); 59 | 60 | test.end(function (this: Test) { 61 | expect(test).toEqual(this); 62 | controller.abort(); 63 | done(); 64 | }); 65 | }, 66 | ); 67 | 68 | app.listen( 69 | { hostname: "localhost", port: await getFreePort(1024), signal }, 70 | ); 71 | }); 72 | 73 | it("superoak(url): .end(cb): should handle error returned when server goes down", async (done) => { 74 | const router = new Router(); 75 | const app = new Application(); 76 | 77 | router.get("/", (ctx) => { 78 | ctx.response.body = ""; 79 | }); 80 | 81 | app.use(router.routes()); 82 | app.use(router.allowedMethods()); 83 | 84 | const controller = new AbortController(); 85 | const { signal } = controller; 86 | 87 | app.addEventListener( 88 | "listen", 89 | async ({ hostname, port, secure }) => { 90 | const protocol = secure ? "https" : "http"; 91 | const url = `${protocol}://${hostname}:${port}`; 92 | 93 | controller.abort(); 94 | 95 | (await superoak(url)) 96 | .get("/") 97 | .expect(200, (err) => { 98 | expect(err).toBeInstanceOf(Error); 99 | done(); 100 | }); 101 | }, 102 | ); 103 | 104 | app.listen( 105 | { hostname: "localhost", port: await getFreePort(1024), signal }, 106 | ); 107 | }); 108 | }); 109 | }); 110 | 111 | describe("superoak(app)", () => { 112 | it("superoak(app): should fire up the app on an ephemeral port", async (done) => { 113 | const router = new Router(); 114 | const app = new Application(); 115 | 116 | router.get("/", (ctx) => { 117 | ctx.response.body = "hey"; 118 | }); 119 | 120 | app.use(router.routes()); 121 | app.use(router.allowedMethods()); 122 | 123 | (await superoak(app)) 124 | .get("/") 125 | .end((_err, res) => { 126 | expect(res.status).toEqual(200); 127 | expect(res.text).toEqual("hey"); 128 | done(); 129 | }); 130 | }); 131 | 132 | it("superoak(app): should not follow redirects by default", async (done) => { 133 | const router = new Router(); 134 | const app = new Application(); 135 | 136 | router.get("/", (ctx) => { 137 | ctx.response.body = "hey"; 138 | }); 139 | 140 | router.get("/redirect", (ctx) => { 141 | ctx.response.redirect("/"); 142 | }); 143 | 144 | app.use(router.routes()); 145 | app.use(router.allowedMethods()); 146 | 147 | const redirects: string[] = []; 148 | 149 | (await superoak(app)) 150 | .get("/redirect") 151 | .on("redirect", (res) => { 152 | redirects.push(res.headers.location); 153 | }) 154 | .then((res) => { 155 | expect(redirects).toEqual([]); 156 | expect(res.status).toEqual(302); 157 | done(); 158 | }); 159 | }); 160 | 161 | it("superoak(app): should follow redirects when instructed", async (done) => { 162 | const router = new Router(); 163 | const app = new Application(); 164 | 165 | router.get("/", (ctx) => { 166 | ctx.response.body = "Smudgie"; 167 | }); 168 | 169 | router.get("/redirect", (ctx) => { 170 | ctx.response.redirect("/"); 171 | }); 172 | 173 | app.use(router.routes()); 174 | app.use(router.allowedMethods()); 175 | 176 | const redirects: string[] = []; 177 | 178 | (await superoak(app)) 179 | .get("/redirect") 180 | .redirects(1) 181 | .on("redirect", (res) => { 182 | redirects.push(res.headers.location); 183 | }) 184 | .then((res) => { 185 | expect(redirects).toEqual(["/"]); 186 | expect(res.status).toEqual(200); 187 | expect(res.text).toEqual("Smudgie"); 188 | done(); 189 | }); 190 | }); 191 | 192 | // TODO: https test. 193 | // it("superoak(app, true): should work with a https server", (done) => {}); 194 | 195 | it("superoak(app): should work with .send() etc", async (done) => { 196 | const router = new Router(); 197 | const app = new Application(); 198 | 199 | router.post("/", async (ctx) => { 200 | const result = await ctx.request.body; 201 | ctx.response.body = result.type() === "json" ? await result.json() : null; 202 | }); 203 | 204 | app.use(router.routes()); 205 | app.use(router.allowedMethods()); 206 | 207 | (await superoak(app)) 208 | .post("/") 209 | .send({ name: "john" }) 210 | .expect("john", () => { 211 | done(); 212 | }); 213 | }); 214 | 215 | describe(".end(fn)", () => { 216 | it("superoak(app): .end(fn): should close server", async (done) => { 217 | const router = new Router(); 218 | const app = new Application(); 219 | 220 | router.get("/", (ctx) => { 221 | ctx.response.body = "superoak FTW!"; 222 | }); 223 | 224 | app.use(router.routes()); 225 | app.use(router.allowedMethods()); 226 | 227 | const portPromise: Promise = new Promise((resolve) => { 228 | app.addEventListener("listen", ({ port }) => resolve(port)); 229 | }); 230 | 231 | (await superoak(app)).get("/") 232 | .end(async (_err, _res) => { 233 | const port: number = await portPromise; 234 | await new Promise((resolve) => setTimeout(resolve, 10)); 235 | const isClosed = await isFreePort(port); 236 | expect(isClosed).toBeTruthy(); 237 | done(); 238 | }); 239 | }); 240 | 241 | // TODO: support nested requests using the same `superoak` instance. 242 | // it("superoak(app): .end(fn): should support nested requests", async ( 243 | // done, 244 | // ) => { 245 | // const router = new Router(); 246 | // const app = new Application(); 247 | 248 | // router.get("/", (ctx) => { 249 | // ctx.response.body = "superoak FTW!"; 250 | // }); 251 | 252 | // app.use(router.routes()); 253 | // app.use(router.allowedMethods()); 254 | 255 | // const test = await superoak(app); 256 | 257 | // test 258 | // .get("/") 259 | // .end(() => { 260 | // test 261 | // .get("/") 262 | // .end((err, res) => { 263 | // expect(err).toBeNull(); 264 | // expect(res.status).toEqual(200); 265 | // expect(res.text).toEqual("superoak FTW!"); 266 | // done(); 267 | // }); 268 | // }); 269 | // }); 270 | 271 | it("superoak(app): .end(fn): should support nested requests if create a new superoak instance", async (done) => { 272 | const router = new Router(); 273 | const app = new Application(); 274 | 275 | router.get("/", (ctx) => { 276 | ctx.response.body = "superoak FTW!"; 277 | }); 278 | 279 | app.use(router.routes()); 280 | app.use(router.allowedMethods()); 281 | 282 | let test = await superoak(app); 283 | 284 | test 285 | .get("/") 286 | .end(async () => { 287 | test = await superoak(app); 288 | 289 | test 290 | .get("/") 291 | .end((err, res) => { 292 | expect(err).toBeNull(); 293 | expect(res.status).toEqual(200); 294 | expect(res.text).toEqual("superoak FTW!"); 295 | done(); 296 | }); 297 | }); 298 | }); 299 | 300 | it("superoak(app): .end(fn): should include the response in the error callback", async (done) => { 301 | const router = new Router(); 302 | const app = new Application(); 303 | 304 | router.get("/", (ctx) => { 305 | ctx.response.body = "whatever"; 306 | }); 307 | 308 | app.use(router.routes()); 309 | app.use(router.allowedMethods()); 310 | 311 | (await superoak(app)) 312 | .get("/") 313 | .expect(() => { 314 | throw new Error("Some error"); 315 | }) 316 | .end((err, res) => { 317 | expect(err).toBeDefined(); 318 | expect(res).toBeDefined(); 319 | // Duck-typing response, just in case. 320 | expect(res.status).toEqual(200); 321 | 322 | done(); 323 | }); 324 | }); 325 | 326 | it("superoak(app): .end(fn): should set `this` to the test object when calling the error callback", async (done) => { 327 | const router = new Router(); 328 | const app = new Application(); 329 | 330 | router.get("/", (ctx) => { 331 | ctx.response.body = "whatever"; 332 | }); 333 | 334 | app.use(router.routes()); 335 | app.use(router.allowedMethods()); 336 | 337 | const test = (await superoak(app)).get("/"); 338 | 339 | test.expect(() => { 340 | throw new Error("Some error"); 341 | }).end(function (this: Test, err, _res) { 342 | expect(err).toBeDefined(); 343 | expect(this).toEqual(test); 344 | done(); 345 | }); 346 | }); 347 | 348 | it("superoak(app): .end(fn): should handle an undefined Response", async (done) => { 349 | const router = new Router(); 350 | const app = new Application(); 351 | 352 | let responsePromise: Promise; 353 | 354 | router.get("/", async (ctx) => { 355 | responsePromise = new Promise((resolve) => { 356 | setTimeout(() => { 357 | ctx.response.body = ""; 358 | resolve(true); 359 | }, 20); 360 | }); 361 | 362 | await responsePromise; 363 | }); 364 | 365 | app.use(router.routes()); 366 | app.use(router.allowedMethods()); 367 | 368 | (await superoak(app)).get("/").timeout(1) 369 | .expect(200, async (err, _res) => { 370 | expect(err).toBeInstanceOf(Error); 371 | await responsePromise; 372 | done(); 373 | }); 374 | }); 375 | 376 | // TODO: determine if _can_ test a server going down? 377 | }); 378 | 379 | describe(".expect(status[, fn])", () => { 380 | it("superoak(app): .expect(status[, fn]): should assert the response status", async (done) => { 381 | const router = new Router(); 382 | const app = new Application(); 383 | 384 | router.get("/", (ctx) => { 385 | ctx.response.body = "hey"; 386 | }); 387 | 388 | app.use(router.routes()); 389 | app.use(router.allowedMethods()); 390 | 391 | (await superoak(app)) 392 | .get("/") 393 | .expect(404) 394 | .end((err, _res) => { 395 | expect(err.message).toEqual('expected 404 "Not Found", got 200 "OK"'); 396 | done(); 397 | }); 398 | }); 399 | }); 400 | 401 | describe(".expect(status)", () => { 402 | it("superoak(app): .expect(status): should assert only status", async (done) => { 403 | const router = new Router(); 404 | const app = new Application(); 405 | 406 | router.get("/", (ctx) => { 407 | ctx.response.body = "hey"; 408 | }); 409 | 410 | app.use(router.routes()); 411 | app.use(router.allowedMethods()); 412 | 413 | (await superoak(app)) 414 | .get("/") 415 | .expect(200) 416 | .end(done); 417 | }); 418 | }); 419 | 420 | describe(".expect(status, body[, fn])", () => { 421 | it("superoak(app): .expect(status, body[, fn]): should assert the response body and status", async (done) => { 422 | const router = new Router(); 423 | const app = new Application(); 424 | 425 | router.get("/", (ctx) => { 426 | ctx.response.body = "foo"; 427 | }); 428 | 429 | app.use(router.routes()); 430 | app.use(router.allowedMethods()); 431 | 432 | (await superoak(app)) 433 | .get("/") 434 | .expect(200, "foo", done); 435 | }); 436 | 437 | describe("when the body argument is an empty string", () => { 438 | it("superoak(app): .expect(status, body[, fn]): should not quietly pass on failure", async (done) => { 439 | const router = new Router(); 440 | const app = new Application(); 441 | 442 | router.get("/", (ctx) => { 443 | ctx.response.body = "foo"; 444 | }); 445 | 446 | app.use(router.routes()); 447 | app.use(router.allowedMethods()); 448 | 449 | (await superoak(app)) 450 | .get("/") 451 | .expect(200, "") 452 | .end((err, _res) => { 453 | expect(err.message).toEqual('expected "" response body, got "foo"'); 454 | done(); 455 | }); 456 | }); 457 | }); 458 | }); 459 | 460 | describe(".expect(body[, fn])", () => { 461 | it("superoak(app): .expect(body[, fn]): should assert the response body", async (done) => { 462 | const router = new Router(); 463 | const app = new Application(); 464 | 465 | router.get("/", (ctx) => { 466 | ctx.response.body = { foo: "bar" }; 467 | }); 468 | 469 | app.use(router.routes()); 470 | app.use(router.allowedMethods()); 471 | 472 | (await superoak(app)) 473 | .get("/") 474 | .expect("hey") 475 | .end((err, _res) => { 476 | expect(err.message).toEqual( 477 | 'expected "hey" response body, got \'{"foo":"bar"}\'', 478 | ); 479 | done(); 480 | }); 481 | }); 482 | 483 | it("superoak(app): .expect(body[, fn]): should assert the status before the body", async (done) => { 484 | const router = new Router(); 485 | const app = new Application(); 486 | 487 | router.get("/", (ctx) => { 488 | ctx.response.status = 500; 489 | ctx.response.body = { message: "something went wrong" }; 490 | }); 491 | 492 | app.use(router.routes()); 493 | app.use(router.allowedMethods()); 494 | 495 | (await superoak(app)) 496 | .get("/") 497 | .expect(200) 498 | .expect("hey") 499 | .end((err, _res) => { 500 | expect(err.message).toEqual( 501 | 'expected 200 "OK", got 500 "Internal Server Error"', 502 | ); 503 | done(); 504 | }); 505 | }); 506 | 507 | it("superoak(app): .expect(body[, fn]): should assert the response text", async (done) => { 508 | const router = new Router(); 509 | const app = new Application(); 510 | 511 | router.get("/", (ctx) => { 512 | ctx.response.body = { foo: "bar" }; 513 | }); 514 | 515 | app.use(router.routes()); 516 | app.use(router.allowedMethods()); 517 | 518 | (await superoak(app)) 519 | .get("/") 520 | .expect('{"foo":"bar"}', done); 521 | }); 522 | 523 | it("superoak(app): .expect(body[, fn]): should assert the parsed response body", async (done) => { 524 | const router = new Router(); 525 | const app = new Application(); 526 | 527 | router.get("/", (ctx) => { 528 | ctx.response.body = { foo: "bar" }; 529 | }); 530 | 531 | app.use(router.routes()); 532 | app.use(router.allowedMethods()); 533 | 534 | (await superoak(app)) 535 | .get("/") 536 | .expect({ foo: "baz" }) 537 | .end(async (err, _res) => { 538 | expect(err.message).toEqual( 539 | 'expected { foo: "baz" } response body, got { foo: "bar" }', 540 | ); 541 | 542 | (await superoak(app)) 543 | .get("/") 544 | .expect({ foo: "bar" }) 545 | .end(done); 546 | }); 547 | }); 548 | 549 | it("superoak(app): .expect(body[, fn]): should test response object types", async (done) => { 550 | const router = new Router(); 551 | const app = new Application(); 552 | 553 | router.get("/", (ctx) => { 554 | ctx.response.status = 200; 555 | ctx.response.body = { stringValue: "foo", numberValue: 3 }; 556 | }); 557 | 558 | app.use(router.routes()); 559 | app.use(router.allowedMethods()); 560 | 561 | (await superoak(app)) 562 | .get("/") 563 | .expect({ stringValue: "foo", numberValue: 3 }, done); 564 | }); 565 | 566 | it("superoak(app): .expect(body[, fn]): should deep test response object types", async (done) => { 567 | const router = new Router(); 568 | const app = new Application(); 569 | 570 | router.get("/", (ctx) => { 571 | ctx.response.status = 200; 572 | ctx.response.body = { 573 | stringValue: "foo", 574 | numberValue: 3, 575 | nestedObject: { innerString: "5" }, 576 | }; 577 | }); 578 | 579 | app.use(router.routes()); 580 | app.use(router.allowedMethods()); 581 | 582 | (await superoak(app)) 583 | .get("/") 584 | .expect( 585 | { 586 | stringValue: "foo", 587 | numberValue: 3, 588 | nestedObject: { innerString: 5 }, 589 | }, 590 | ) 591 | .end(async (err, _res) => { 592 | expect(err.message).toEqual( 593 | 'expected {\n stringValue: "foo",\n numberValue: 3,\n nestedObject: { innerString: 5 }\n} response body, got {\n stringValue: "foo",\n numberValue: 3,\n nestedObject: { innerString: "5" }\n}', 594 | ); // eslint-disable-line max-len 595 | 596 | (await superoak(app)) 597 | .get("/") 598 | .expect( 599 | { 600 | stringValue: "foo", 601 | numberValue: 3, 602 | nestedObject: { innerString: "5" }, 603 | }, 604 | ) 605 | .end(done); 606 | }); 607 | }); 608 | 609 | it("superoak(app): .expect(body[, fn]): should support regular expressions", async (done) => { 610 | const router = new Router(); 611 | const app = new Application(); 612 | 613 | router.get("/", (ctx) => { 614 | ctx.response.body = "foobar"; 615 | }); 616 | 617 | app.use(router.routes()); 618 | app.use(router.allowedMethods()); 619 | 620 | (await superoak(app)) 621 | .get("/") 622 | .expect(/^bar/) 623 | .end((err, _res) => { 624 | expect(err.message).toEqual('expected body "foobar" to match /^bar/'); 625 | done(); 626 | }); 627 | }); 628 | }); 629 | 630 | it("superoak(app): .expect(body[, fn]): should assert response body multiple times", async (done) => { 631 | const router = new Router(); 632 | const app = new Application(); 633 | 634 | router.get("/", (ctx) => { 635 | ctx.response.body = "hey deno"; 636 | }); 637 | 638 | app.use(router.routes()); 639 | app.use(router.allowedMethods()); 640 | 641 | (await superoak(app)) 642 | .get("/") 643 | .expect(/deno/) 644 | .expect("hey") 645 | .expect("hey deno") 646 | .end((err, _res) => { 647 | expect(err.message).toEqual( 648 | 'expected "hey" response body, got "hey deno"', 649 | ); 650 | done(); 651 | }); 652 | 653 | it("superoak(app): .expect(body[, fn]): should assert response body multiple times with no exception", async (done) => { 654 | const router = new Router(); 655 | const app = new Application(); 656 | 657 | router.get("/", (ctx) => { 658 | ctx.response.body = "hey deno"; 659 | }); 660 | 661 | app.use(router.routes()); 662 | app.use(router.allowedMethods()); 663 | 664 | (await superoak(app)) 665 | .get("/") 666 | .expect(/deno/) 667 | .expect(/^hey/) 668 | .expect("hey deno", done); 669 | }); 670 | }); 671 | 672 | describe(".expect(field, value[, fn])", () => { 673 | it("superoak(app): .expect(field, value[, fn]): should assert the header field presence", async (done) => { 674 | const router = new Router(); 675 | const app = new Application(); 676 | 677 | router.get("/", (ctx) => { 678 | ctx.response.body = { foo: "bar" }; 679 | }); 680 | 681 | app.use(router.routes()); 682 | app.use(router.allowedMethods()); 683 | 684 | (await superoak(app)) 685 | .get("/") 686 | .expect("Content-Foo", "bar") 687 | .end((err, _res) => { 688 | expect(err.message).toEqual('expected "Content-Foo" header field'); 689 | done(); 690 | }); 691 | }); 692 | 693 | it("superoak(app): .expect(field, value[, fn]): should assert the header field value", async (done) => { 694 | const router = new Router(); 695 | const app = new Application(); 696 | 697 | router.get("/", (ctx) => { 698 | ctx.response.body = { foo: "bar" }; 699 | }); 700 | 701 | app.use(router.routes()); 702 | app.use(router.allowedMethods()); 703 | 704 | (await superoak(app)) 705 | .get("/") 706 | .expect("Content-Type", "text/plain") 707 | .end((err, _res) => { 708 | expect(err.message).toEqual( 709 | 'expected "Content-Type" of "text/plain", got "application/json; charset=UTF-8"', 710 | ); 711 | done(); 712 | }); 713 | }); 714 | 715 | it("superoak(app): .expect(field, value[, fn]): should assert multiple fields", async (done) => { 716 | const router = new Router(); 717 | const app = new Application(); 718 | 719 | router.get("/", (ctx) => { 720 | ctx.response.body = "hey"; 721 | }); 722 | 723 | app.use(router.routes()); 724 | app.use(router.allowedMethods()); 725 | 726 | (await superoak(app)) 727 | .get("/") 728 | .expect("Content-Type", "text/plain; charset=UTF-8") 729 | .expect("Content-Length", "3") 730 | .end(done); 731 | }); 732 | 733 | it("superoak(app): .expect(field, value[, fn]): should support regular expressions", async (done) => { 734 | const router = new Router(); 735 | const app = new Application(); 736 | 737 | router.get("/", (ctx) => { 738 | ctx.response.body = "hey"; 739 | }); 740 | 741 | app.use(router.routes()); 742 | app.use(router.allowedMethods()); 743 | 744 | (await superoak(app)) 745 | .get("/") 746 | .expect("Content-Type", /^application/) 747 | .end((err) => { 748 | expect(err.message).toEqual( 749 | 'expected "Content-Type" matching /^application/, got "text/plain; charset=UTF-8"', 750 | ); 751 | done(); 752 | }); 753 | }); 754 | 755 | it("superoak(app): .expect(field, value[, fn]): should support numbers", async (done) => { 756 | const router = new Router(); 757 | const app = new Application(); 758 | 759 | router.get("/", (ctx) => { 760 | ctx.response.body = "hey"; 761 | }); 762 | 763 | app.use(router.routes()); 764 | app.use(router.allowedMethods()); 765 | 766 | (await superoak(app)) 767 | .get("/") 768 | .expect("Content-Length", 4) 769 | .end((err) => { 770 | expect(err.message).toEqual( 771 | 'expected "Content-Length" of "4", got "3"', 772 | ); 773 | done(); 774 | }); 775 | }); 776 | 777 | describe("handling arbitrary expect functions", () => { 778 | const router = new Router(); 779 | const app = new Application(); 780 | 781 | router.get("/", (ctx) => { 782 | ctx.response.body = "hey"; 783 | }); 784 | 785 | app.use(router.routes()); 786 | app.use(router.allowedMethods()); 787 | 788 | it("superoak(app): .expect(field, value[, fn]): reports errors", async (done) => { 789 | (await superoak(app)).get("/") 790 | .expect((_res) => { 791 | throw new Error("failed"); 792 | }) 793 | .end((err) => { 794 | expect(err.message).toEqual("failed"); 795 | done(); 796 | }); 797 | }); 798 | 799 | it( 800 | "superoak(app): .expect(field, value[, fn]): ensures truthy non-errors returned from asserts are not promoted to errors", 801 | async (done) => { 802 | (await superoak(app)).get("/") 803 | .expect((_res) => { 804 | return "some descriptive error"; 805 | }) 806 | .end((err) => { 807 | expect(err).toBeNull(); 808 | done(); 809 | }); 810 | }, 811 | ); 812 | 813 | it("superoak(app): .expect(field, value[, fn]): ensures truthy errors returned from asserts are throw to end", async (done) => { 814 | (await superoak(app)).get("/") 815 | .expect((_res) => { 816 | return new Error("some descriptive error"); 817 | }) 818 | .end((err) => { 819 | expect(err.message).toEqual("some descriptive error"); 820 | expect(err).toBeInstanceOf(Error); 821 | done(); 822 | }); 823 | }); 824 | 825 | it("superoak(app): .expect(field, value[, fn]): doesn't create false negatives", async (done) => { 826 | (await superoak(app)).get("/") 827 | .expect((_res) => { 828 | }) 829 | .end(done); 830 | }); 831 | 832 | it("superoak(app): .expect(field, value[, fn]): handles multiple asserts", async (done) => { 833 | const calls: number[] = []; 834 | 835 | (await superoak(app)).get("/") 836 | .expect((_res) => { 837 | calls[0] = 1; 838 | }) 839 | .expect((_res) => { 840 | calls[1] = 1; 841 | }) 842 | .expect((_res) => { 843 | calls[2] = 1; 844 | }) 845 | .end(() => { 846 | const callCount = [0, 1, 2].reduce((count, i) => { 847 | return count + calls[i]; 848 | }, 0); 849 | expect(callCount).toEqual(3); 850 | done(); 851 | }); 852 | }); 853 | 854 | it("superoak(app): .expect(field, value[, fn]): plays well with normal assertions - no false positives", async (done) => { 855 | (await superoak(app)).get("/") 856 | .expect((_res) => { 857 | }) 858 | .expect("Content-Type", /json/) 859 | .end((err) => { 860 | expect(err.message).toMatch(/Content-Type/); 861 | done(); 862 | }); 863 | }); 864 | 865 | it("superoak(app): .expect(field, value[, fn]): plays well with normal assertions - no false negatives", async (done) => { 866 | (await superoak(app)).get("/") 867 | .expect((_res) => { 868 | }) 869 | .expect("Content-Type", /plain/) 870 | .expect((_res) => { 871 | }) 872 | .expect("Content-Type", /text/) 873 | .end(done); 874 | }); 875 | }); 876 | 877 | describe("handling multiple assertions per field", () => { 878 | it("superoak(app): .expect(field, value[, fn]): should work", async (done) => { 879 | const router = new Router(); 880 | const app = new Application(); 881 | 882 | router.get("/", (ctx) => { 883 | ctx.response.body = "hey"; 884 | }); 885 | 886 | app.use(router.routes()); 887 | app.use(router.allowedMethods()); 888 | 889 | (await superoak(app)) 890 | .get("/") 891 | .expect("Content-Type", /text/) 892 | .expect("Content-Type", /plain/) 893 | .end(done); 894 | }); 895 | 896 | it("superoak(app): .expect(field, value[, fn]): should return an error if the first one fails", async (done) => { 897 | const router = new Router(); 898 | const app = new Application(); 899 | 900 | router.get("/", (ctx) => { 901 | ctx.response.body = "hey"; 902 | }); 903 | 904 | app.use(router.routes()); 905 | app.use(router.allowedMethods()); 906 | 907 | (await superoak(app)) 908 | .get("/") 909 | .expect("Content-Type", /bloop/) 910 | .expect("Content-Type", /plain/) 911 | .end((err) => { 912 | expect(err.message).toEqual( 913 | 'expected "Content-Type" matching /bloop/, ' + 914 | 'got "text/plain; charset=UTF-8"', 915 | ); 916 | done(); 917 | }); 918 | }); 919 | 920 | it("superoak(app): .expect(field, value[, fn]): should return an error if a middle one fails", async (done) => { 921 | const router = new Router(); 922 | const app = new Application(); 923 | 924 | router.get("/", (ctx) => { 925 | ctx.response.body = "hey"; 926 | }); 927 | 928 | app.use(router.routes()); 929 | app.use(router.allowedMethods()); 930 | 931 | (await superoak(app)) 932 | .get("/") 933 | .expect("Content-Type", /text/) 934 | .expect("Content-Type", /bloop/) 935 | .expect("Content-Type", /plain/) 936 | .end((err) => { 937 | expect(err.message).toEqual( 938 | 'expected "Content-Type" matching /bloop/, ' + 939 | 'got "text/plain; charset=UTF-8"', 940 | ); 941 | done(); 942 | }); 943 | }); 944 | 945 | it("superoak(app): .expect(field, value[, fn]): should return an error if the last one fails", async (done) => { 946 | const router = new Router(); 947 | const app = new Application(); 948 | 949 | router.get("/", (ctx) => { 950 | ctx.response.body = "hey"; 951 | }); 952 | 953 | app.use(router.routes()); 954 | app.use(router.allowedMethods()); 955 | 956 | (await superoak(app)) 957 | .get("/") 958 | .expect("Content-Type", /text/) 959 | .expect("Content-Type", /plain/) 960 | .expect("Content-Type", /bloop/) 961 | .end((err) => { 962 | expect(err.message).toEqual( 963 | 'expected "Content-Type" matching /bloop/, ' + 964 | 'got "text/plain; charset=UTF-8"', 965 | ); 966 | done(); 967 | }); 968 | }); 969 | }); 970 | }); 971 | }); 972 | -------------------------------------------------------------------------------- /test/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Test timeout. 3 | */ 4 | export const TEST_TIMEOUT = 3000; 5 | 6 | /** 7 | * A no-op _describe_ method. 8 | * 9 | * @param name 10 | * @param fn 11 | */ 12 | export function describe(_name: string, fn: () => void | Promise) { 13 | return fn(); 14 | } 15 | 16 | export type Done = (err?: unknown) => void; 17 | 18 | /** 19 | * An _it_ wrapper around `Deno.test`. 20 | * 21 | * @param name 22 | * @param fn 23 | */ 24 | export function it( 25 | name: string, 26 | fn: (done: Done) => void | Promise, 27 | options?: Partial, 28 | ) { 29 | Deno.test({ 30 | ...options, 31 | name, 32 | fn: async () => { 33 | let testError: unknown; 34 | 35 | let done: Done = (err?: unknown) => { 36 | if (err) { 37 | testError = err; 38 | } 39 | }; 40 | 41 | let race: Promise = Promise.resolve(); 42 | let timeoutId: number; 43 | 44 | if (fn.length === 1) { 45 | let resolve: (value?: unknown) => void; 46 | const donePromise = new Promise((r) => { 47 | resolve = r; 48 | }); 49 | 50 | race = Promise.race([ 51 | new Promise((_, reject) => 52 | timeoutId = setTimeout(() => { 53 | clearTimeout(timeoutId); 54 | 55 | reject( 56 | new Error( 57 | `test "${name}" failed to complete by calling "done" within ${TEST_TIMEOUT}ms.`, 58 | ), 59 | ); 60 | }, TEST_TIMEOUT) 61 | ), 62 | donePromise, 63 | ]); 64 | 65 | done = (err?: unknown) => { 66 | clearTimeout(timeoutId); 67 | resolve(); 68 | 69 | if (err) { 70 | testError = err; 71 | } 72 | }; 73 | } 74 | 75 | await Promise.allSettled([fn(done), race]); 76 | 77 | if (timeoutId!) { 78 | clearTimeout(timeoutId); 79 | } 80 | 81 | // REF: https://github.com/denoland/deno/blob/987716798fb3bddc9abc7e12c25a043447be5280/ext/timers/01_timers.js#L353 82 | await new Promise((resolve) => setTimeout(resolve, 20)); 83 | 84 | if (testError) { 85 | throw testError; 86 | } 87 | }, 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Version of SuperOak. 3 | */ 4 | export const VERSION = "5.0.0"; 5 | 6 | /** 7 | * Supported versions of Deno. 8 | */ 9 | export const DENO_SUPPORTED_VERSIONS = ["2.3.3"]; 10 | --------------------------------------------------------------------------------