├── .vscode ├── settings.json └── launch.json ├── .github ├── codecov.yml └── workflows │ └── ci.yml ├── mod.ts ├── examples ├── user.ts ├── user_nested_test.ts └── user_flat_test.ts ├── test_deps.ts ├── LICENSE ├── README.md ├── describe.ts ├── test_suite.ts └── describe_test.ts /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.unstable": false, 4 | "deno.lint": true 5 | } 6 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | codecov: 3 | require_ci_to_pass: true 4 | coverage: 5 | status: 6 | project: 7 | default: 8 | informational: true 9 | ignore: 10 | - examples 11 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export { 2 | afterAll, 3 | afterEach, 4 | beforeAll, 5 | beforeEach, 6 | describe, 7 | it, 8 | } from "./describe.ts"; 9 | export type { 10 | DescribeArgs, 11 | DescribeDefinition, 12 | ItArgs, 13 | ItDefinition, 14 | TestSuite, 15 | } from "./describe.ts"; 16 | -------------------------------------------------------------------------------- /examples/user.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | constructor(public name: string) { 3 | users.set(name, this); 4 | } 5 | } 6 | 7 | const users: Map = new Map(); 8 | 9 | export function getUser(name: string): User | void { 10 | return users.get(name); 11 | } 12 | 13 | export function resetUsers(): void { 14 | users.clear(); 15 | } 16 | -------------------------------------------------------------------------------- /test_deps.ts: -------------------------------------------------------------------------------- 1 | export { 2 | assert, 3 | assertEquals, 4 | AssertionError, 5 | assertObjectMatch, 6 | assertRejects, 7 | assertStrictEquals, 8 | assertThrows, 9 | } from "https://deno.land/std@0.135.0/testing/asserts.ts"; 10 | 11 | export { 12 | assertSpyCall, 13 | assertSpyCalls, 14 | spy, 15 | stub, 16 | } from "https://deno.land/std@0.135.0/testing/mock.ts"; 17 | export type { 18 | Spy, 19 | SpyCall, 20 | Stub, 21 | } from "https://deno.land/std@0.135.0/testing/mock.ts"; 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "deno run", 6 | "type": "pwa-node", 7 | "request": "launch", 8 | "cwd": "${workspaceFolder}", 9 | "runtimeExecutable": "deno", 10 | "runtimeArgs": ["run", "--inspect-brk", "${file}"], 11 | "attachSimplePort": 9229 12 | }, 13 | { 14 | "name": "deno test", 15 | "type": "pwa-node", 16 | "request": "launch", 17 | "cwd": "${workspaceFolder}", 18 | "runtimeExecutable": "deno", 19 | "runtimeArgs": ["test", "--inspect-brk", "${file}"], 20 | "attachSimplePort": 9229 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /examples/user_nested_test.ts: -------------------------------------------------------------------------------- 1 | import { afterEach, beforeEach, describe, it } from "../mod.ts"; 2 | import { assertEquals } from "../test_deps.ts"; 3 | import { getUser, resetUsers, User } from "./user.ts"; 4 | 5 | describe("user describe", () => { 6 | let user: User; 7 | 8 | beforeEach(() => { 9 | user = new User("Kyle June"); 10 | }); 11 | 12 | afterEach(() => { 13 | resetUsers(); 14 | }); 15 | 16 | it("create", () => { 17 | const user = new User("John Doe"); 18 | assertEquals(user.name, "John Doe"); 19 | }); 20 | 21 | describe("getUser", () => { 22 | it("user does not exist", () => { 23 | assertEquals(getUser("John Doe"), undefined); 24 | }); 25 | 26 | it("user exists", () => { 27 | assertEquals(getUser("Kyle June"), user); 28 | }); 29 | }); 30 | 31 | it("resetUsers", () => { 32 | assertEquals(getUser("Kyle June"), user); 33 | resetUsers(); 34 | assertEquals(getUser("Kyle June"), undefined); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Udibo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /examples/user_flat_test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from "../mod.ts"; 2 | import { assertEquals } from "../test_deps.ts"; 3 | import { getUser, resetUsers, User } from "./user.ts"; 4 | 5 | interface UserContext { 6 | user: User; 7 | } 8 | 9 | const userSuite = describe({ 10 | name: "user", 11 | beforeEach(this: UserContext) { 12 | this.user = new User("Kyle June"); 13 | }, 14 | afterEach() { 15 | resetUsers(); 16 | }, 17 | }); 18 | 19 | it(userSuite, "create", () => { 20 | const user = new User("John Doe"); 21 | assertEquals(user.name, "John Doe"); 22 | }); 23 | 24 | const getUserSuite = describe({ 25 | name: "getUser", 26 | suite: userSuite, 27 | }); 28 | 29 | it(getUserSuite, "user does not exist", () => { 30 | assertEquals(getUser("John Doe"), undefined); 31 | }); 32 | 33 | it(getUserSuite, "user exists", function (this: UserContext) { 34 | assertEquals(getUser("Kyle June"), this.user); 35 | }); 36 | 37 | it(userSuite, "resetUsers", function (this: UserContext) { 38 | assertEquals(getUser("Kyle June"), this.user); 39 | resetUsers(); 40 | assertEquals(getUser("Kyle June"), undefined); 41 | }); 42 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | name: test deno ${{ matrix.deno }} ${{ matrix.os }} 6 | runs-on: ${{ matrix.os }} 7 | timeout-minutes: 5 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, windows-latest, macOS-latest] 11 | deno: [v1.x] 12 | fail-fast: true 13 | steps: 14 | - name: Clone repository 15 | uses: actions/checkout@v2 16 | - name: Setup deno 17 | uses: denoland/setup-deno@main 18 | with: 19 | deno-version: ${{ matrix.deno }} 20 | - name: Check formatting 21 | if: matrix.os == 'ubuntu-latest' 22 | run: deno fmt --check 23 | - name: Check linting 24 | if: matrix.os == 'ubuntu-latest' 25 | run: deno lint 26 | - name: Run tests 27 | run: deno test --coverage=cov 28 | - name: Run tests unstable 29 | run: deno test --unstable 30 | - name: Generate lcov 31 | if: | 32 | matrix.os == 'ubuntu-latest' && 33 | matrix.deno == 'v1.x' 34 | run: deno coverage --lcov cov > cov.lcov 35 | - name: Upload coverage 36 | if: | 37 | matrix.os == 'ubuntu-latest' && 38 | matrix.deno == 'v1.x' 39 | uses: codecov/codecov-action@v1 40 | with: 41 | files: cov.lcov 42 | - name: Release info 43 | if: | 44 | github.repository == 'udibo/test_suite' && 45 | matrix.os == 'ubuntu-latest' && 46 | matrix.deno == 'v1.x' && 47 | startsWith(github.ref, 'refs/tags/') 48 | shell: bash 49 | run: | 50 | echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV 51 | - name: Release 52 | uses: softprops/action-gh-release@v1 53 | if: env.RELEASE_VERSION != '' 54 | env: 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | with: 57 | draft: true 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Test Suite 2 | 3 | [![version](https://img.shields.io/badge/release-0.16.1-success)](https://deno.land/x/test_suite@0.16.1) 4 | [![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/deno.land/x/test_suite@0.16.1/mod.ts) 5 | [![CI](https://github.com/udibo/test_suite/workflows/CI/badge.svg)](https://github.com/udibo/test_suite/actions?query=workflow%3ACI) 6 | [![codecov](https://codecov.io/gh/udibo/test_suite/branch/main/graph/badge.svg?token=EFKGY72AAV)](https://codecov.io/gh/udibo/test_suite) 7 | [![license](https://img.shields.io/github/license/udibo/test_suite)](https://github.com/udibo/test_suite/blob/master/LICENSE) 8 | 9 | An extension of Deno's built-in test runner to add setup/teardown hooks and make 10 | it easier to organize tests in a format similar to Jasmine, Jest, and Mocha. 11 | 12 | This module is being archived. It has been added to Deno's standard library in 13 | the testing directory. Use Deno's standard library instead. 14 | 15 | ## Features 16 | 17 | - Ability to group tests together into test suites 18 | - Setup/teardown hooks for test suites (beforeAll, afterAll, beforeEach, 19 | afterEach) 20 | - Tests within a test suite inherit configuration options 21 | - describe/it functions similar to Jasmine, Jest, and Mocha 22 | - shorthand for focusing and ignoring tests 23 | 24 | ## Installation 25 | 26 | To include this module in a Deno project, you can import directly from the TS 27 | files. This module is available in Deno's third part module registry but can 28 | also be imported directly from GitHub using raw content URLs. 29 | 30 | ```ts 31 | // Import from Deno's third party module registry 32 | import { describe, it } from "https://deno.land/x/test_suite@0.16.1/mod.ts"; 33 | // Import from GitHub 34 | import { 35 | describe, 36 | it, 37 | } from "https://raw.githubusercontent.com/udibo/test_suite/0.16.1/mod.ts"; 38 | ``` 39 | 40 | ## Usage 41 | 42 | When you have a set of tests that are related, you can group them together by 43 | creating a test suite. A test suite can contain other test suites and tests. All 44 | tests within a suite will inherit their options from the suite unless they 45 | specifically set them. 46 | 47 | The beforeAll and afterAll hook options can be used to do something before and 48 | after all the tests in the suite run. If you would like to set values for all 49 | tests within the suite, you can create a context interface that defines all the 50 | values available to the tests that are defined in the beforeAll function using 51 | the this argument. An example of this can be found in the section for 52 | [flat test grouping](#flat-test-grouping). 53 | 54 | The beforeEach and afterEach hook options are similar to beforeAll and afterAll 55 | except they are called before and after each individual test. 56 | 57 | Below are some examples of how to use `describe` and `it` in tests. 58 | 59 | See 60 | [deno docs](https://doc.deno.land/https/deno.land/x/test_suite@0.16.1/mod.ts) 61 | for more information. 62 | 63 | ### Nested test grouping 64 | 65 | The example below can be found [here](examples/user_nested_test.ts). 66 | 67 | ```ts 68 | import { 69 | afterEach, 70 | beforeEach, 71 | describe, 72 | it, 73 | } from "https://deno.land/x/test_suite@0.16.1/mod.ts"; 74 | import { assertEquals } from "https://deno.land/std@0.135.0/testing/asserts.ts"; 75 | import { 76 | getUser, 77 | resetUsers, 78 | User, 79 | } from "https://deno.land/x/test_suite@0.16.1/examples/user.ts"; 80 | 81 | describe("user describe", () => { 82 | let user: User; 83 | 84 | beforeEach(() => { 85 | user = new User("Kyle June"); 86 | }); 87 | 88 | afterEach(() => { 89 | resetUsers(); 90 | }); 91 | 92 | it("create", () => { 93 | const user = new User("John Doe"); 94 | assertEquals(user.name, "John Doe"); 95 | }); 96 | 97 | describe("getUser", () => { 98 | it("user does not exist", () => { 99 | assertEquals(getUser("John Doe"), undefined); 100 | }); 101 | 102 | it("user exists", () => { 103 | assertEquals(getUser("Kyle June"), user); 104 | }); 105 | }); 106 | 107 | it("resetUsers", () => { 108 | assertEquals(getUser("Kyle June"), user); 109 | resetUsers(); 110 | assertEquals(getUser("Kyle June"), undefined); 111 | }); 112 | }); 113 | ``` 114 | 115 | If you run the above tests using `deno test`, you will get the following output. 116 | 117 | ```sh 118 | $ deno test 119 | running 1 test from file:///examples/user_nested_test.ts 120 | test user describe ... 121 | test create ... ok (4ms) 122 | test getUser ... 123 | test user does not exist ... ok (4ms) 124 | test user exists ... ok (3ms) 125 | ok (11ms) 126 | test resetUsers ... ok (3ms) 127 | ok (24ms) 128 | 129 | test result: ok. 1 passed (5 steps); 0 failed; 0 ignored; 0 measured; 0 filtered out (43ms) 130 | ``` 131 | 132 | ### Flat test grouping 133 | 134 | The example below can be found [here](examples/user_flat_test.ts). 135 | 136 | ```ts 137 | import { describe, it } from "https://deno.land/x/test_suite@0.16.1/mod.ts"; 138 | import { assertEquals } from "https://deno.land/std@0.135.0/testing/asserts.ts"; 139 | import { 140 | getUser, 141 | resetUsers, 142 | User, 143 | } from "https://deno.land/x/test_suite@0.16.1/examples/user.ts"; 144 | 145 | interface UserContext { 146 | user: User; 147 | } 148 | 149 | const userSuite = describe({ 150 | name: "user", 151 | beforeEach(this: UserContext) { 152 | this.user = new User("Kyle June"); 153 | }, 154 | afterEach() { 155 | resetUsers(); 156 | }, 157 | }); 158 | 159 | it(userSuite, "create", () => { 160 | const user = new User("John Doe"); 161 | assertEquals(user.name, "John Doe"); 162 | }); 163 | 164 | const getUserSuite = describe({ 165 | name: "getUser", 166 | suite: userSuite, 167 | }); 168 | 169 | it(getUserSuite, "user does not exist", () => { 170 | assertEquals(getUser("John Doe"), undefined); 171 | }); 172 | 173 | it(getUserSuite, "user exists", function (this: UserContext) { 174 | assertEquals(getUser("Kyle June"), this.user); 175 | }); 176 | 177 | it(userSuite, "resetUsers", function (this: UserContext) { 178 | assertEquals(getUser("Kyle June"), this.user); 179 | resetUsers(); 180 | assertEquals(getUser("Kyle June"), undefined); 181 | }); 182 | ``` 183 | 184 | If you run the above tests using `deno test`, you will get the following output. 185 | 186 | ```sh 187 | $ deno test 188 | running 1 test from file:///examples/user_flat_test.ts 189 | test user ... 190 | test create ... ok (3ms) 191 | test getUser ... 192 | test user does not exist ... ok (2ms) 193 | test user exists ... ok (4ms) 194 | ok (11ms) 195 | test resetUsers ... ok (3ms) 196 | ok (22ms) 197 | 198 | test result: ok. 1 passed (5 steps); 0 failed; 0 ignored; 0 measured; 0 filtered out (44ms) 199 | ``` 200 | 201 | ### Shorthand for focusing and ignoring tests 202 | 203 | To avoid having to change your test function arguments to be able to focus or 204 | ignore tests temporarily, a shorthand has been implemented. This makes it as 205 | easy as typing `.only` and `.ignore` to focus and ignore tests. 206 | 207 | - To focus a test case, replace `it` with `it.only`. 208 | - To ignore a test case, replace `it` with `it.ignore`. 209 | - To focus a test suite, replace `describe` with `describe.only`. 210 | - To ignore a test suite, replace `describe` with `describe.ignore`. 211 | 212 | ### Migrating from earlier versions 213 | 214 | The `TestSuite` class has been turned into an internal only class. To create a 215 | test suite, use the `describe` function instead of creating a new instance of 216 | `TestSuite` directly. 217 | 218 | The `test` function has been removed. Replace `test` calls with calls to `it`. 219 | 220 | The `each` function that was previously available has been temporarily removed 221 | to allow me to switch over to the test step api quicker. I plan on implementing 222 | this later. If you make use of the `each` function for generating test cases, do 223 | not upgrade beyond version 0.9.5. 224 | 225 | ## License 226 | 227 | [MIT](LICENSE) 228 | -------------------------------------------------------------------------------- /describe.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DescribeDefinition, 3 | HookNames, 4 | ItDefinition, 5 | TestSuite, 6 | TestSuiteInternal, 7 | } from "./test_suite.ts"; 8 | export type { DescribeDefinition, ItDefinition, TestSuite }; 9 | 10 | /** The arguments for an ItFunction. */ 11 | export type ItArgs = 12 | | [options: ItDefinition] 13 | | [ 14 | name: string, 15 | options: Omit, "name">, 16 | ] 17 | | [ 18 | name: string, 19 | fn: (this: T) => void | Promise, 20 | ] 21 | | [fn: (this: T) => void | Promise] 22 | | [ 23 | name: string, 24 | options: Omit, "fn" | "name">, 25 | fn: (this: T) => void | Promise, 26 | ] 27 | | [ 28 | options: Omit, "fn">, 29 | fn: (this: T) => void | Promise, 30 | ] 31 | | [ 32 | options: Omit, "fn" | "name">, 33 | fn: (this: T) => void | Promise, 34 | ] 35 | | [ 36 | suite: TestSuite, 37 | name: string, 38 | options: Omit, "name" | "suite">, 39 | ] 40 | | [ 41 | suite: TestSuite, 42 | name: string, 43 | fn: (this: T) => void | Promise, 44 | ] 45 | | [ 46 | suite: TestSuite, 47 | fn: (this: T) => void | Promise, 48 | ] 49 | | [ 50 | suite: TestSuite, 51 | name: string, 52 | options: Omit, "fn" | "name" | "suite">, 53 | fn: (this: T) => void | Promise, 54 | ] 55 | | [ 56 | suite: TestSuite, 57 | options: Omit, "fn" | "suite">, 58 | fn: (this: T) => void | Promise, 59 | ] 60 | | [ 61 | suite: TestSuite, 62 | options: Omit, "fn" | "name" | "suite">, 63 | fn: (this: T) => void | Promise, 64 | ]; 65 | 66 | /** Generates an ItDefinition from ItArgs. */ 67 | function itDefinition(...args: ItArgs): ItDefinition { 68 | let [ 69 | suiteOptionsOrNameOrFn, 70 | optionsOrNameOrFn, 71 | optionsOrFn, 72 | fn, 73 | ] = args; 74 | let suite: TestSuite | undefined = undefined; 75 | let name: string; 76 | let options: 77 | | ItDefinition 78 | | Omit, "fn"> 79 | | Omit, "name"> 80 | | Omit, "fn" | "name">; 81 | if ( 82 | typeof suiteOptionsOrNameOrFn === "object" && 83 | typeof (suiteOptionsOrNameOrFn as TestSuite).symbol === "symbol" 84 | ) { 85 | suite = suiteOptionsOrNameOrFn as TestSuite; 86 | } else { 87 | fn = optionsOrFn as typeof fn; 88 | optionsOrFn = optionsOrNameOrFn as typeof optionsOrFn; 89 | optionsOrNameOrFn = suiteOptionsOrNameOrFn as typeof optionsOrNameOrFn; 90 | } 91 | if (typeof optionsOrNameOrFn === "string") { 92 | name = optionsOrNameOrFn; 93 | if (typeof optionsOrFn === "function") { 94 | fn = optionsOrFn; 95 | options = {}; 96 | } else { 97 | options = optionsOrFn!; 98 | if (!fn) fn = (options as Omit, "name">).fn; 99 | } 100 | } else if (typeof optionsOrNameOrFn === "function") { 101 | fn = optionsOrNameOrFn; 102 | name = fn.name; 103 | options = {}; 104 | } else { 105 | options = optionsOrNameOrFn!; 106 | if (typeof optionsOrFn === "function") { 107 | fn = optionsOrFn; 108 | } else { 109 | fn = (options as ItDefinition).fn; 110 | } 111 | name = (options as ItDefinition).name ?? fn.name; 112 | } 113 | 114 | return { 115 | suite, 116 | ...options, 117 | name, 118 | fn, 119 | }; 120 | } 121 | 122 | /** Registers an individual test case. */ 123 | export interface it { 124 | (...args: ItArgs): void; 125 | 126 | /** Registers an individual test case with only set to true. */ 127 | only(...args: ItArgs): void; 128 | 129 | /** Registers an individual test case with ignore set to true. */ 130 | ignore(...args: ItArgs): void; 131 | } 132 | 133 | /** 134 | * Registers an individual test case. 135 | * 136 | * @deprecated Use https://deno.land/std/testing/bdd.ts instead. 137 | */ 138 | export function it(...args: ItArgs): void { 139 | if (TestSuiteInternal.running) { 140 | throw new Error( 141 | "cannot register new test cases after already registered test cases start running", 142 | ); 143 | } 144 | const options = itDefinition(...args); 145 | const { suite } = options; 146 | const testSuite = suite 147 | ? TestSuiteInternal.suites.get(suite.symbol) 148 | : TestSuiteInternal.current; 149 | 150 | if (!TestSuiteInternal.started) TestSuiteInternal.started = true; 151 | if (testSuite) { 152 | TestSuiteInternal.addStep(testSuite, options); 153 | } else { 154 | const { 155 | name, 156 | fn, 157 | ignore, 158 | only, 159 | permissions, 160 | sanitizeExit, 161 | sanitizeOps, 162 | sanitizeResources, 163 | } = options; 164 | TestSuiteInternal.registerTest({ 165 | name, 166 | ignore, 167 | only, 168 | permissions, 169 | sanitizeExit, 170 | sanitizeOps, 171 | sanitizeResources, 172 | async fn() { 173 | if (!TestSuiteInternal.running) TestSuiteInternal.running = true; 174 | await fn.call({} as T); 175 | }, 176 | }); 177 | } 178 | } 179 | 180 | it.only = function itOnly(...args: ItArgs): void { 181 | const options = itDefinition(...args); 182 | return it({ 183 | ...options, 184 | only: true, 185 | }); 186 | }; 187 | 188 | it.ignore = function itIgnore(...args: ItArgs): void { 189 | const options = itDefinition(...args); 190 | return it({ 191 | ...options, 192 | ignore: true, 193 | }); 194 | }; 195 | 196 | function addHook( 197 | name: HookNames, 198 | fn: (this: T) => void | Promise, 199 | ): void { 200 | if (!TestSuiteInternal.current) { 201 | if (TestSuiteInternal.started) { 202 | throw new Error( 203 | "cannot add global hooks after a global test is registered", 204 | ); 205 | } 206 | TestSuiteInternal.current = new TestSuiteInternal({ 207 | name: "global", 208 | [name]: fn, 209 | }); 210 | } else { 211 | TestSuiteInternal.setHook(TestSuiteInternal.current!, name, fn); 212 | } 213 | } 214 | 215 | /** Run some shared setup before all of the tests in the suite. */ 216 | export function beforeAll( 217 | fn: (this: T) => void | Promise, 218 | ): void { 219 | addHook("beforeAll", fn); 220 | } 221 | 222 | /** Run some shared teardown after all of the tests in the suite. */ 223 | export function afterAll( 224 | fn: (this: T) => void | Promise, 225 | ): void { 226 | addHook("afterAll", fn); 227 | } 228 | 229 | /** Run some shared setup before each test in the suite. */ 230 | export function beforeEach( 231 | fn: (this: T) => void | Promise, 232 | ): void { 233 | addHook("beforeEach", fn); 234 | } 235 | 236 | /** Run some shared teardown after each test in the suite. */ 237 | export function afterEach( 238 | fn: (this: T) => void | Promise, 239 | ): void { 240 | addHook("afterEach", fn); 241 | } 242 | 243 | /** The arguments for a DescribeFunction. */ 244 | export type DescribeArgs = 245 | | [options: DescribeDefinition] 246 | | [name: string] 247 | | [ 248 | name: string, 249 | options: Omit, "name">, 250 | ] 251 | | [name: string, fn: () => void] 252 | | [fn: () => void] 253 | | [ 254 | name: string, 255 | options: Omit, "fn" | "name">, 256 | fn: () => void, 257 | ] 258 | | [ 259 | options: Omit, "fn">, 260 | fn: () => void, 261 | ] 262 | | [ 263 | options: Omit, "fn" | "name">, 264 | fn: () => void, 265 | ] 266 | | [ 267 | suite: TestSuite, 268 | name: string, 269 | ] 270 | | [ 271 | suite: TestSuite, 272 | name: string, 273 | options: Omit, "name" | "suite">, 274 | ] 275 | | [ 276 | suite: TestSuite, 277 | name: string, 278 | fn: () => void, 279 | ] 280 | | [ 281 | suite: TestSuite, 282 | fn: () => void, 283 | ] 284 | | [ 285 | suite: TestSuite, 286 | name: string, 287 | options: Omit, "fn" | "name" | "suite">, 288 | fn: () => void, 289 | ] 290 | | [ 291 | suite: TestSuite, 292 | options: Omit, "fn" | "suite">, 293 | fn: () => void, 294 | ] 295 | | [ 296 | suite: TestSuite, 297 | options: Omit, "fn" | "name" | "suite">, 298 | fn: () => void, 299 | ]; 300 | 301 | /** Generates a DescribeDefinition from DescribeArgs. */ 302 | function describeDefinition( 303 | ...args: DescribeArgs 304 | ): DescribeDefinition { 305 | let [ 306 | suiteOptionsOrNameOrFn, 307 | optionsOrNameOrFn, 308 | optionsOrFn, 309 | fn, 310 | ] = args; 311 | let suite: TestSuite | undefined = undefined; 312 | let name: string; 313 | let options: 314 | | DescribeDefinition 315 | | Omit, "fn"> 316 | | Omit, "name"> 317 | | Omit, "fn" | "name">; 318 | if ( 319 | typeof suiteOptionsOrNameOrFn === "object" && 320 | typeof (suiteOptionsOrNameOrFn as TestSuite).symbol === "symbol" 321 | ) { 322 | suite = suiteOptionsOrNameOrFn as TestSuite; 323 | } else { 324 | fn = optionsOrFn as typeof fn; 325 | optionsOrFn = optionsOrNameOrFn as typeof optionsOrFn; 326 | optionsOrNameOrFn = suiteOptionsOrNameOrFn as typeof optionsOrNameOrFn; 327 | } 328 | if (typeof optionsOrNameOrFn === "string") { 329 | name = optionsOrNameOrFn; 330 | if (typeof optionsOrFn === "function") { 331 | fn = optionsOrFn; 332 | options = {}; 333 | } else { 334 | options = optionsOrFn ?? {}; 335 | if (!fn) fn = (options as Omit, "name">).fn; 336 | } 337 | } else if (typeof optionsOrNameOrFn === "function") { 338 | fn = optionsOrNameOrFn; 339 | name = fn.name; 340 | options = {}; 341 | } else { 342 | options = optionsOrNameOrFn ?? {}; 343 | if (typeof optionsOrFn === "function") { 344 | fn = optionsOrFn; 345 | } else { 346 | fn = (options as DescribeDefinition).fn; 347 | } 348 | name = (options as DescribeDefinition).name ?? fn?.name ?? ""; 349 | } 350 | 351 | if (!suite) { 352 | suite = options.suite; 353 | } 354 | if (!suite && TestSuiteInternal.current) { 355 | const { symbol } = TestSuiteInternal.current; 356 | suite = { symbol }; 357 | } 358 | 359 | return { 360 | ...options, 361 | suite, 362 | name, 363 | fn, 364 | }; 365 | } 366 | 367 | /** Registers a test suite. */ 368 | export interface describe { 369 | (...args: DescribeArgs): TestSuite; 370 | 371 | /** Registers a test suite with only set to true. */ 372 | only(...args: DescribeArgs): TestSuite; 373 | 374 | /** Registers a test suite with ignore set to true. */ 375 | ignore(...args: DescribeArgs): TestSuite; 376 | } 377 | 378 | /** 379 | * Registers a test suite. 380 | * 381 | * @deprecated Use https://deno.land/std/testing/bdd.ts instead. 382 | */ 383 | export function describe( 384 | ...args: DescribeArgs 385 | ): TestSuite { 386 | if (TestSuiteInternal.running) { 387 | throw new Error( 388 | "cannot register new test suites after already registered test cases start running", 389 | ); 390 | } 391 | const options = describeDefinition(...args); 392 | if (!TestSuiteInternal.started) TestSuiteInternal.started = true; 393 | const { symbol } = new TestSuiteInternal(options); 394 | return { symbol }; 395 | } 396 | 397 | describe.only = function describeOnly( 398 | ...args: DescribeArgs 399 | ): TestSuite { 400 | const options = describeDefinition(...args); 401 | return describe({ 402 | ...options, 403 | only: true, 404 | }); 405 | }; 406 | 407 | describe.ignore = function describeIgnore( 408 | ...args: DescribeArgs 409 | ): TestSuite { 410 | const options = describeDefinition(...args); 411 | return describe({ 412 | ...options, 413 | ignore: true, 414 | }); 415 | }; 416 | -------------------------------------------------------------------------------- /test_suite.ts: -------------------------------------------------------------------------------- 1 | /** The options for creating a test suite with the describe function. */ 2 | export interface DescribeDefinition extends Omit { 3 | fn?: () => void; 4 | /** 5 | * The `describe` function returns a `TestSuite` representing the group of tests. 6 | * If `describe` is called within another `describe` calls `fn`, the suite will default to that parent `describe` calls returned `TestSuite`. 7 | * If `describe` is not called within another `describe` calls `fn`, the suite will default to the `TestSuite` representing the global group of tests. 8 | */ 9 | suite?: TestSuite; 10 | /** Run some shared setup before all of the tests in the suite. */ 11 | beforeAll?: 12 | | ((this: T) => void | Promise) 13 | | ((this: T) => void | Promise)[]; 14 | /** Run some shared teardown after all of the tests in the suite. */ 15 | afterAll?: 16 | | ((this: T) => void | Promise) 17 | | ((this: T) => void | Promise)[]; 18 | /** Run some shared setup before each test in the suite. */ 19 | beforeEach?: 20 | | ((this: T) => void | Promise) 21 | | ((this: T) => void | Promise)[]; 22 | /** Run some shared teardown after each test in the suite. */ 23 | afterEach?: 24 | | ((this: T) => void | Promise) 25 | | ((this: T) => void | Promise)[]; 26 | } 27 | 28 | /** The options for creating an individual test case with the it function. */ 29 | export interface ItDefinition extends Omit { 30 | fn: (this: T) => void | Promise; 31 | /** 32 | * The `describe` function returns a `TestSuite` representing the group of tests. 33 | * If `it` is called within a `describe` calls `fn`, the suite will default to that parent `describe` calls returned `TestSuite`. 34 | * If `it` is not called within a `describe` calls `fn`, the suite will default to the `TestSuite` representing the global group of tests. 35 | */ 36 | suite?: TestSuite; 37 | } 38 | 39 | /** The names of all the different types of hooks. */ 40 | export type HookNames = "beforeAll" | "afterAll" | "beforeEach" | "afterEach"; 41 | 42 | /** Optional test definition keys. */ 43 | const optionalTestDefinitionKeys: (keyof Deno.TestDefinition)[] = [ 44 | "only", 45 | "permissions", 46 | "ignore", 47 | "sanitizeExit", 48 | "sanitizeOps", 49 | "sanitizeResources", 50 | ]; 51 | 52 | /** Optional test step definition keys. */ 53 | const optionalTestStepDefinitionKeys: (keyof Deno.TestStepDefinition)[] = [ 54 | "ignore", 55 | "sanitizeExit", 56 | "sanitizeOps", 57 | "sanitizeResources", 58 | ]; 59 | 60 | /** 61 | * A group of tests. 62 | */ 63 | export interface TestSuite { 64 | symbol: symbol; 65 | } 66 | 67 | /** 68 | * An internal representation of a group of tests. 69 | */ 70 | export class TestSuiteInternal implements TestSuite { 71 | symbol: symbol; 72 | protected describe: DescribeDefinition; 73 | protected steps: (TestSuiteInternal | ItDefinition)[]; 74 | protected hasOnlyStep: boolean; 75 | 76 | constructor(describe: DescribeDefinition) { 77 | this.describe = describe; 78 | this.steps = []; 79 | this.hasOnlyStep = false; 80 | 81 | const { suite } = describe; 82 | if (suite && !TestSuiteInternal.suites.has(suite.symbol)) { 83 | throw new Error("suite does not represent a registered test suite"); 84 | } 85 | const testSuite = suite 86 | ? TestSuiteInternal.suites.get(suite.symbol) 87 | : TestSuiteInternal.current; 88 | this.symbol = Symbol(); 89 | TestSuiteInternal.suites.set(this.symbol, this); 90 | 91 | const { fn } = describe; 92 | if (fn) { 93 | const temp = TestSuiteInternal.current; 94 | TestSuiteInternal.current = this; 95 | try { 96 | fn(); 97 | } finally { 98 | TestSuiteInternal.current = temp; 99 | } 100 | } 101 | 102 | if (testSuite) { 103 | TestSuiteInternal.addStep(testSuite, this); 104 | } else { 105 | const { 106 | name, 107 | ignore, 108 | only, 109 | permissions, 110 | sanitizeExit, 111 | sanitizeOps, 112 | sanitizeResources, 113 | } = describe; 114 | TestSuiteInternal.registerTest({ 115 | name, 116 | ignore, 117 | only, 118 | permissions, 119 | sanitizeExit, 120 | sanitizeOps, 121 | sanitizeResources, 122 | fn: async (t) => { 123 | if (!TestSuiteInternal.running) TestSuiteInternal.running = true; 124 | const context = {} as T; 125 | const { beforeAll } = this.describe; 126 | if (typeof beforeAll === "function") { 127 | await beforeAll.call(context); 128 | } else if (beforeAll) { 129 | for (const hook of beforeAll) { 130 | await hook.call(context); 131 | } 132 | } 133 | try { 134 | TestSuiteInternal.active.push(this.symbol); 135 | await TestSuiteInternal.run(this, context, t); 136 | } finally { 137 | TestSuiteInternal.active.pop(); 138 | const { afterAll } = this.describe; 139 | if (typeof afterAll === "function") { 140 | await afterAll.call(context); 141 | } else if (afterAll) { 142 | for (const hook of afterAll) { 143 | await hook.call(context); 144 | } 145 | } 146 | } 147 | }, 148 | }); 149 | } 150 | } 151 | 152 | /** If the test cases have begun executing. */ 153 | static running = false; 154 | 155 | /** If a test has been registered yet. Block adding global hooks if a test has been registered. */ 156 | static started = false; 157 | 158 | /** A map of all test suites by symbol. */ 159 | // deno-lint-ignore no-explicit-any 160 | static suites = new Map>(); 161 | 162 | /** The current test suite being registered. */ 163 | // deno-lint-ignore no-explicit-any 164 | static current: TestSuiteInternal | null = null; 165 | 166 | /** The stack of tests that are actively running. */ 167 | static active: symbol[] = []; 168 | 169 | /** This is used internally for testing this module. */ 170 | static reset(): void { 171 | TestSuiteInternal.running = false; 172 | TestSuiteInternal.started = false; 173 | TestSuiteInternal.current = null; 174 | TestSuiteInternal.active = []; 175 | } 176 | 177 | /** This is used internally to register tests. */ 178 | static registerTest(options: Deno.TestDefinition): void { 179 | options = { ...options }; 180 | optionalTestDefinitionKeys.forEach((key) => { 181 | if (typeof options[key] === "undefined") delete options[key]; 182 | }); 183 | Deno.test(options); 184 | } 185 | 186 | /** Updates all steps within top level suite to have ignore set to true if only is not set to true on step. */ 187 | static addingOnlyStep(suite: TestSuiteInternal) { 188 | if (!suite.hasOnlyStep) { 189 | for (let i = 0; i < suite.steps.length; i++) { 190 | const step = suite.steps[i]!; 191 | if (!(step instanceof TestSuiteInternal) && !step.only) { 192 | suite.steps.splice(i--, 1); 193 | } 194 | } 195 | suite.hasOnlyStep = true; 196 | } 197 | 198 | const parentSuite = suite.describe.suite; 199 | const parentTestSuite = parentSuite && 200 | TestSuiteInternal.suites.get(parentSuite.symbol); 201 | if (parentTestSuite) { 202 | TestSuiteInternal.addingOnlyStep(parentTestSuite); 203 | } 204 | } 205 | 206 | /** This is used internally to add steps to a test suite. */ 207 | static addStep( 208 | suite: TestSuiteInternal, 209 | step: TestSuiteInternal | ItDefinition, 210 | ): void { 211 | if (!suite.hasOnlyStep) { 212 | if (step instanceof TestSuiteInternal) { 213 | if (step.hasOnlyStep || step.describe.only) { 214 | TestSuiteInternal.addingOnlyStep(suite); 215 | } 216 | } else { 217 | if (step.only) TestSuiteInternal.addingOnlyStep(suite); 218 | } 219 | } 220 | 221 | if ( 222 | !(suite.hasOnlyStep && !(step instanceof TestSuiteInternal) && !step.only) 223 | ) { 224 | suite.steps.push(step); 225 | } 226 | } 227 | 228 | /** This is used internally to add hooks to a test suite. */ 229 | static setHook( 230 | suite: TestSuiteInternal, 231 | name: HookNames, 232 | fn: (this: T) => void | Promise, 233 | ): void { 234 | if (suite.describe[name]) { 235 | if (typeof suite.describe[name] === "function") { 236 | suite.describe[name] = [ 237 | suite.describe[name] as ((this: T) => void | Promise), 238 | ]; 239 | } 240 | (suite.describe[name] as ((this: T) => void | Promise)[]).push(fn); 241 | } else { 242 | suite.describe[name] = fn; 243 | } 244 | } 245 | 246 | /** This is used internally to run all steps for a test suite. */ 247 | static async run( 248 | suite: TestSuiteInternal, 249 | context: T, 250 | t: Deno.TestContext, 251 | ): Promise { 252 | const hasOnly = suite.hasOnlyStep || suite.describe.only || false; 253 | for (const step of suite.steps) { 254 | if ( 255 | hasOnly && step instanceof TestSuiteInternal && 256 | !(step.hasOnlyStep || step.describe.only || false) 257 | ) { 258 | continue; 259 | } 260 | 261 | const { 262 | name, 263 | fn, 264 | ignore, 265 | permissions, 266 | sanitizeExit, 267 | sanitizeOps, 268 | sanitizeResources, 269 | } = step instanceof TestSuiteInternal ? step.describe : step; 270 | 271 | const options: Deno.TestStepDefinition = { 272 | name, 273 | ignore, 274 | sanitizeExit, 275 | sanitizeOps, 276 | sanitizeResources, 277 | fn: async (t) => { 278 | if (permissions) { 279 | throw new Error( 280 | "permissions option not available for nested tests", 281 | ); 282 | } 283 | context = { ...context }; 284 | if (step instanceof TestSuiteInternal) { 285 | const { beforeAll } = step.describe; 286 | if (typeof beforeAll === "function") { 287 | await beforeAll.call(context); 288 | } else if (beforeAll) { 289 | for (const hook of beforeAll) { 290 | await hook.call(context); 291 | } 292 | } 293 | try { 294 | TestSuiteInternal.active.push(step.symbol); 295 | await TestSuiteInternal.run(step, context, t); 296 | } finally { 297 | TestSuiteInternal.active.pop(); 298 | const { afterAll } = step.describe; 299 | if (typeof afterAll === "function") { 300 | await afterAll.call(context); 301 | } else if (afterAll) { 302 | for (const hook of afterAll) { 303 | await hook.call(context); 304 | } 305 | } 306 | } 307 | } else { 308 | await TestSuiteInternal.runTest(fn!, context); 309 | } 310 | }, 311 | }; 312 | optionalTestStepDefinitionKeys.forEach((key) => { 313 | if (typeof options[key] === "undefined") delete options[key]; 314 | }); 315 | await t.step(options); 316 | } 317 | } 318 | 319 | static async runTest( 320 | fn: (this: T) => void | Promise, 321 | context: T, 322 | activeIndex = 0, 323 | ) { 324 | const suite = TestSuiteInternal.active[activeIndex]; 325 | const testSuite = suite && TestSuiteInternal.suites.get(suite); 326 | if (testSuite) { 327 | context = { ...context }; 328 | const { beforeEach } = testSuite.describe; 329 | if (typeof beforeEach === "function") { 330 | await beforeEach.call(context); 331 | } else if (beforeEach) { 332 | for (const hook of beforeEach) { 333 | await hook.call(context); 334 | } 335 | } 336 | try { 337 | await TestSuiteInternal.runTest(fn, context, activeIndex + 1); 338 | } finally { 339 | const { afterEach } = testSuite.describe; 340 | if (typeof afterEach === "function") { 341 | await afterEach.call(context); 342 | } else if (afterEach) { 343 | for (const hook of afterEach) { 344 | await hook.call(context); 345 | } 346 | } 347 | } 348 | } else { 349 | await fn.call(context); 350 | } 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /describe_test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assert, 3 | assertEquals, 4 | assertObjectMatch, 5 | assertStrictEquals, 6 | } from "./test_deps.ts"; 7 | import { 8 | afterAll, 9 | afterEach, 10 | beforeAll, 11 | beforeEach, 12 | describe, 13 | it, 14 | } from "./describe.ts"; 15 | import { TestSuiteInternal } from "./test_suite.ts"; 16 | import { assertSpyCall, assertSpyCalls, Spy, spy, stub } from "./test_deps.ts"; 17 | 18 | Deno.test("global", async (t) => { 19 | class TestContext implements Deno.TestContext { 20 | steps: TestContext[]; 21 | spies: { 22 | step: Spy; 23 | }; 24 | 25 | constructor() { 26 | this.spies = { 27 | step: spy(this, "step"), 28 | }; 29 | this.steps = []; 30 | } 31 | 32 | async step(t: Deno.TestStepDefinition): Promise; 33 | async step( 34 | name: string, 35 | fn: (t: Deno.TestContext) => void | Promise, 36 | ): Promise; 37 | async step( 38 | tOrName: Deno.TestStepDefinition | string, 39 | fn?: (t: Deno.TestContext) => void | Promise, 40 | ): Promise { 41 | let ignore = false; 42 | if (typeof tOrName === "object") { 43 | ignore = tOrName.ignore ?? false; 44 | fn = tOrName.fn; 45 | } 46 | 47 | const context = new TestContext(); 48 | this.steps.push(context); 49 | if (!ignore) { 50 | await fn!(context); 51 | } 52 | return !ignore; 53 | } 54 | } 55 | 56 | const baseStepOptions: Omit = { 57 | ignore: false, 58 | sanitizeExit: true, 59 | sanitizeOps: true, 60 | sanitizeResources: true, 61 | }; 62 | 63 | const baseOptions: Omit = { 64 | ...baseStepOptions, 65 | only: false, 66 | permissions: "inherit", 67 | }; 68 | 69 | interface GlobalContext { 70 | allTimer: number; 71 | eachTimer: number; 72 | } 73 | 74 | let timerIdx = 1; 75 | const timers = new Map(); 76 | function hookFns() { 77 | timerIdx = 1; 78 | timers.clear(); 79 | return { 80 | beforeAllFn: spy(async function (this: GlobalContext) { 81 | await Promise.resolve(); 82 | this.allTimer = timerIdx++; 83 | timers.set(this.allTimer, setTimeout(() => {}, 10000)); 84 | }), 85 | afterAllFn: spy(async function (this: GlobalContext) { 86 | await Promise.resolve(); 87 | clearTimeout(timers.get(this.allTimer)); 88 | }), 89 | beforeEachFn: spy(async function (this: GlobalContext) { 90 | await Promise.resolve(); 91 | this.eachTimer = timerIdx++; 92 | timers.set(this.eachTimer, setTimeout(() => {}, 10000)); 93 | }), 94 | afterEachFn: spy(async function (this: GlobalContext) { 95 | await Promise.resolve(); 96 | clearTimeout(timers.get(this.eachTimer)); 97 | }), 98 | }; 99 | } 100 | 101 | await t.step("global hooks", async () => { 102 | const test = stub(Deno, "test"), 103 | fns = [spy(), spy()], 104 | { beforeAllFn, afterAllFn, beforeEachFn, afterEachFn } = hookFns(); 105 | 106 | try { 107 | beforeAll(beforeAllFn); 108 | afterAll(afterAllFn); 109 | 110 | beforeEach(beforeEachFn); 111 | afterEach(afterEachFn); 112 | 113 | assertEquals(it({ name: "example 1", fn: fns[0] }), undefined); 114 | assertEquals(it({ name: "example 2", fn: fns[1] }), undefined); 115 | 116 | assertSpyCalls(fns[0], 0); 117 | assertSpyCalls(fns[1], 0); 118 | 119 | assertSpyCall(test, 0); 120 | const call = test.calls[0]; 121 | const options = call.args[0] as Deno.TestDefinition; 122 | assertEquals(Object.keys(options).sort(), ["fn", "name"]); 123 | assertEquals(options.name, "global"); 124 | 125 | const context = new TestContext(); 126 | const result = options.fn(context); 127 | assertStrictEquals(Promise.resolve(result), result); 128 | assertEquals(await result, undefined); 129 | assertSpyCalls(context.spies.step, 2); 130 | } finally { 131 | TestSuiteInternal.reset(); 132 | test.restore(); 133 | } 134 | 135 | let fn = fns[0]; 136 | assertSpyCall(fn, 0, { 137 | self: { allTimer: 1, eachTimer: 2 }, 138 | args: [], 139 | returned: undefined, 140 | }); 141 | assertSpyCalls(fn, 1); 142 | 143 | fn = fns[1]; 144 | assertSpyCall(fn, 0, { 145 | self: { allTimer: 1, eachTimer: 3 }, 146 | args: [], 147 | returned: undefined, 148 | }); 149 | assertSpyCalls(fn, 1); 150 | 151 | assertSpyCalls(beforeAllFn, 1); 152 | assertSpyCalls(afterAllFn, 1); 153 | assertSpyCalls(beforeEachFn, 2); 154 | assertSpyCalls(afterEachFn, 2); 155 | }); 156 | 157 | await t.step("it", async (t) => { 158 | async function assertOptions( 159 | expectedOptions: Omit, 160 | cb: (fn: Spy) => void, 161 | ): Promise { 162 | const test = stub(Deno, "test"); 163 | const fn = spy(); 164 | try { 165 | cb(fn); 166 | 167 | assertSpyCalls(fn, 0); 168 | assertSpyCall(test, 0); 169 | const call = test.calls[0]; 170 | const options = call.args[0] as Deno.TestDefinition; 171 | assertEquals( 172 | Object.keys(options).sort(), 173 | ["name", "fn", ...Object.keys(expectedOptions)].sort(), 174 | ); 175 | assertObjectMatch(options, { 176 | name: "example", 177 | ...expectedOptions, 178 | }); 179 | 180 | const context = new TestContext(); 181 | const result = options.fn(context); 182 | assertStrictEquals(Promise.resolve(result), result); 183 | assertEquals(await result, undefined); 184 | assertSpyCalls(context.spies.step, 0); 185 | assertSpyCall(fn, 0, { 186 | self: {}, 187 | args: [], 188 | returned: undefined, 189 | }); 190 | } finally { 191 | TestSuiteInternal.reset(); 192 | test.restore(); 193 | } 194 | } 195 | 196 | async function assertMinimumOptions( 197 | cb: (fn: Spy) => void, 198 | ): Promise { 199 | await assertOptions({}, cb); 200 | } 201 | 202 | async function assertAllOptions( 203 | cb: (fn: Spy) => void, 204 | ): Promise { 205 | await assertOptions(baseOptions, cb); 206 | } 207 | 208 | await t.step("signature 1", async (t) => { 209 | await t.step( 210 | "minimum options", 211 | async () => 212 | await assertMinimumOptions((fn) => { 213 | assertEquals(it({ name: "example", fn }), undefined); 214 | }), 215 | ); 216 | 217 | await t.step("all options", async () => 218 | await assertAllOptions((fn) => { 219 | assertEquals( 220 | it({ 221 | name: "example", 222 | fn, 223 | ...baseOptions, 224 | }), 225 | undefined, 226 | ); 227 | })); 228 | }); 229 | 230 | await t.step("signature 2", async (t) => { 231 | await t.step( 232 | "minimum options", 233 | async () => 234 | await assertMinimumOptions((fn) => { 235 | assertEquals(it("example", { fn }), undefined); 236 | }), 237 | ); 238 | 239 | await t.step("all options", async () => 240 | await assertAllOptions((fn) => { 241 | assertEquals( 242 | it("example", { 243 | fn, 244 | ...baseOptions, 245 | }), 246 | undefined, 247 | ); 248 | })); 249 | }); 250 | 251 | await t.step("signature 3", async () => 252 | await assertMinimumOptions((fn) => { 253 | assertEquals(it("example", fn), undefined); 254 | })); 255 | 256 | await t.step("signature 4", async () => 257 | await assertMinimumOptions((fn) => { 258 | assertEquals( 259 | it(function example(this: void, ...args) { 260 | fn.apply(this, args); 261 | }), 262 | undefined, 263 | ); 264 | })); 265 | 266 | await t.step("signature 5", async (t) => { 267 | await t.step( 268 | "minimum options", 269 | async () => 270 | await assertMinimumOptions((fn) => { 271 | assertEquals(it("example", {}, fn), undefined); 272 | }), 273 | ); 274 | 275 | await t.step("all options", async () => 276 | await assertAllOptions((fn) => { 277 | assertEquals( 278 | it("example", { 279 | ...baseOptions, 280 | }, fn), 281 | undefined, 282 | ); 283 | })); 284 | }); 285 | 286 | await t.step("signature 6", async (t) => { 287 | await t.step( 288 | "minimum options", 289 | async () => 290 | await assertMinimumOptions((fn) => { 291 | assertEquals(it({ name: "example" }, fn), undefined); 292 | }), 293 | ); 294 | 295 | await t.step("all options", async () => 296 | await assertAllOptions((fn) => { 297 | assertEquals( 298 | it({ 299 | name: "example", 300 | ...baseOptions, 301 | }, fn), 302 | undefined, 303 | ); 304 | })); 305 | }); 306 | 307 | await t.step("signature 7", async (t) => { 308 | await t.step( 309 | "minimum options", 310 | async () => 311 | await assertMinimumOptions((fn) => { 312 | assertEquals( 313 | it({}, function example(this: void, ...args) { 314 | fn.apply(this, args); 315 | }), 316 | undefined, 317 | ); 318 | }), 319 | ); 320 | 321 | await t.step("all options", async () => 322 | await assertAllOptions((fn) => { 323 | assertEquals( 324 | it({ 325 | ...baseOptions, 326 | }, function example(this: void, ...args) { 327 | fn.apply(this, args); 328 | }), 329 | undefined, 330 | ); 331 | })); 332 | }); 333 | 334 | await t.step("only", async (t) => { 335 | async function assertMinimumOptions( 336 | cb: (fn: Spy) => void, 337 | ): Promise { 338 | await assertOptions({ only: true }, cb); 339 | } 340 | 341 | async function assertAllOptions( 342 | cb: (fn: Spy) => void, 343 | ): Promise { 344 | await assertOptions({ ...baseOptions, only: true }, cb); 345 | } 346 | 347 | await t.step("signature 1", async (t) => { 348 | await t.step( 349 | "minimum options", 350 | async () => 351 | await assertMinimumOptions((fn) => { 352 | assertEquals(it.only({ name: "example", fn }), undefined); 353 | }), 354 | ); 355 | 356 | await t.step("all options", async () => 357 | await assertAllOptions((fn) => { 358 | assertEquals( 359 | it.only({ 360 | name: "example", 361 | fn, 362 | ...baseOptions, 363 | }), 364 | undefined, 365 | ); 366 | })); 367 | }); 368 | 369 | await t.step("signature 2", async (t) => { 370 | await t.step( 371 | "minimum options", 372 | async () => 373 | await assertMinimumOptions((fn) => { 374 | assertEquals(it.only("example", { fn }), undefined); 375 | }), 376 | ); 377 | 378 | await t.step("all options", async () => 379 | await assertAllOptions((fn) => { 380 | assertEquals( 381 | it.only("example", { 382 | fn, 383 | ...baseOptions, 384 | }), 385 | undefined, 386 | ); 387 | })); 388 | }); 389 | 390 | await t.step( 391 | "signature 3", 392 | async () => 393 | await assertMinimumOptions((fn) => { 394 | assertEquals(it.only("example", fn), undefined); 395 | }), 396 | ); 397 | 398 | await t.step( 399 | "signature 4", 400 | async () => 401 | await assertMinimumOptions((fn) => { 402 | assertEquals( 403 | it.only(function example(this: void, ...args) { 404 | fn.apply(this, args); 405 | }), 406 | undefined, 407 | ); 408 | }), 409 | ); 410 | 411 | await t.step("signature 5", async (t) => { 412 | await t.step( 413 | "minimum options", 414 | async () => 415 | await assertMinimumOptions((fn) => { 416 | assertEquals(it.only("example", {}, fn), undefined); 417 | }), 418 | ); 419 | 420 | await t.step("all options", async () => 421 | await assertAllOptions((fn) => { 422 | assertEquals( 423 | it.only("example", { 424 | ...baseOptions, 425 | }, fn), 426 | undefined, 427 | ); 428 | })); 429 | }); 430 | 431 | await t.step("signature 6", async (t) => { 432 | await t.step( 433 | "minimum options", 434 | async () => 435 | await assertMinimumOptions((fn) => { 436 | assertEquals(it.only({ name: "example" }, fn), undefined); 437 | }), 438 | ); 439 | 440 | await t.step("all options", async () => 441 | await assertAllOptions((fn) => { 442 | assertEquals( 443 | it.only({ 444 | name: "example", 445 | ...baseOptions, 446 | }, fn), 447 | undefined, 448 | ); 449 | })); 450 | }); 451 | 452 | await t.step("signature 7", async (t) => { 453 | await t.step( 454 | "minimum options", 455 | async () => 456 | await assertMinimumOptions((fn) => { 457 | assertEquals( 458 | it.only({}, function example(this: void, ...args) { 459 | fn.apply(this, args); 460 | }), 461 | undefined, 462 | ); 463 | }), 464 | ); 465 | 466 | await t.step("all options", async () => 467 | await assertAllOptions((fn) => { 468 | assertEquals( 469 | it.only({ 470 | ...baseOptions, 471 | }, function example(this: void, ...args) { 472 | fn.apply(this, args); 473 | }), 474 | undefined, 475 | ); 476 | })); 477 | }); 478 | }); 479 | 480 | await t.step("ignore", async (t) => { 481 | async function assertMinimumOptions( 482 | cb: (fn: Spy) => void, 483 | ): Promise { 484 | await assertOptions({ ignore: true }, cb); 485 | } 486 | 487 | async function assertAllOptions( 488 | cb: (fn: Spy) => void, 489 | ): Promise { 490 | await assertOptions({ ...baseOptions, ignore: true }, cb); 491 | } 492 | 493 | await t.step("signature 1", async (t) => { 494 | await t.step( 495 | "minimum options", 496 | async () => 497 | await assertMinimumOptions((fn) => { 498 | assertEquals(it.ignore({ name: "example", fn }), undefined); 499 | }), 500 | ); 501 | 502 | await t.step("all options", async () => 503 | await assertAllOptions((fn) => { 504 | assertEquals( 505 | it.ignore({ 506 | name: "example", 507 | fn, 508 | ...baseOptions, 509 | }), 510 | undefined, 511 | ); 512 | })); 513 | }); 514 | 515 | await t.step("signature 2", async (t) => { 516 | await t.step( 517 | "minimum options", 518 | async () => 519 | await assertMinimumOptions((fn) => { 520 | assertEquals(it.ignore("example", { fn }), undefined); 521 | }), 522 | ); 523 | 524 | await t.step("all options", async () => 525 | await assertAllOptions((fn) => { 526 | assertEquals( 527 | it.ignore("example", { 528 | fn, 529 | ...baseOptions, 530 | }), 531 | undefined, 532 | ); 533 | })); 534 | }); 535 | 536 | await t.step( 537 | "signature 3", 538 | async () => 539 | await assertMinimumOptions((fn) => { 540 | assertEquals(it.ignore("example", fn), undefined); 541 | }), 542 | ); 543 | 544 | await t.step( 545 | "signature 4", 546 | async () => 547 | await assertMinimumOptions((fn) => { 548 | assertEquals( 549 | it.ignore(function example(this: void, ...args) { 550 | fn.apply(this, args); 551 | }), 552 | undefined, 553 | ); 554 | }), 555 | ); 556 | 557 | await t.step("signature 5", async (t) => { 558 | await t.step( 559 | "minimum options", 560 | async () => 561 | await assertMinimumOptions((fn) => { 562 | assertEquals(it.ignore("example", {}, fn), undefined); 563 | }), 564 | ); 565 | 566 | await t.step("all options", async () => 567 | await assertAllOptions((fn) => { 568 | assertEquals( 569 | it.ignore("example", { 570 | ...baseOptions, 571 | }, fn), 572 | undefined, 573 | ); 574 | })); 575 | }); 576 | 577 | await t.step("signature 6", async (t) => { 578 | await t.step( 579 | "minimum options", 580 | async () => 581 | await assertMinimumOptions((fn) => { 582 | assertEquals(it.ignore({ name: "example" }, fn), undefined); 583 | }), 584 | ); 585 | 586 | await t.step("all options", async () => 587 | await assertAllOptions((fn) => { 588 | assertEquals( 589 | it.ignore({ 590 | name: "example", 591 | ...baseOptions, 592 | }, fn), 593 | undefined, 594 | ); 595 | })); 596 | }); 597 | 598 | await t.step("signature 7", async (t) => { 599 | await t.step( 600 | "minimum options", 601 | async () => 602 | await assertMinimumOptions((fn) => { 603 | assertEquals( 604 | it.ignore({}, function example(this: void, ...args) { 605 | fn.apply(this, args); 606 | }), 607 | undefined, 608 | ); 609 | }), 610 | ); 611 | 612 | await t.step("all options", async () => 613 | await assertAllOptions((fn) => { 614 | assertEquals( 615 | it.ignore({ 616 | ...baseOptions, 617 | }, function example(this: void, ...args) { 618 | fn.apply(this, args); 619 | }), 620 | undefined, 621 | ); 622 | })); 623 | }); 624 | }); 625 | }); 626 | 627 | await t.step("describe", async (t) => { 628 | async function assertOptions( 629 | expectedOptions: Omit, 630 | cb: (fns: Spy[]) => void, 631 | ): Promise { 632 | const test = stub(Deno, "test"); 633 | const fns = [spy(), spy()]; 634 | try { 635 | cb(fns); 636 | 637 | assertSpyCall(test, 0); 638 | const call = test.calls[0]; 639 | const options = call.args[0] as Deno.TestDefinition; 640 | assertEquals( 641 | Object.keys(options).sort(), 642 | ["name", "fn", ...Object.keys(expectedOptions)].sort(), 643 | ); 644 | assertObjectMatch(options, { 645 | name: "example", 646 | ...expectedOptions, 647 | }); 648 | 649 | assertSpyCalls(fns[0], 0); 650 | assertSpyCalls(fns[1], 0); 651 | 652 | const context = new TestContext(); 653 | const result = options.fn(context); 654 | assertStrictEquals(Promise.resolve(result), result); 655 | assertEquals(await result, undefined); 656 | assertSpyCalls(context.spies.step, 2); 657 | 658 | let fn = fns[0]; 659 | assertSpyCall(fn, 0, { 660 | self: {}, 661 | args: [], 662 | returned: undefined, 663 | }); 664 | 665 | fn = fns[1]; 666 | assertSpyCall(fn, 0, { 667 | self: {}, 668 | args: [], 669 | returned: undefined, 670 | }); 671 | assertSpyCalls(fn, 1); 672 | } finally { 673 | TestSuiteInternal.reset(); 674 | test.restore(); 675 | } 676 | } 677 | 678 | async function assertMinimumOptions( 679 | cb: (fns: Spy[]) => void, 680 | ): Promise { 681 | await assertOptions({}, cb); 682 | } 683 | 684 | async function assertAllOptions( 685 | cb: (fns: Spy[]) => void, 686 | ): Promise { 687 | await assertOptions({ ...baseOptions }, cb); 688 | } 689 | 690 | await t.step("signature 1", async (t) => { 691 | await t.step( 692 | "minimum options", 693 | async () => 694 | await assertMinimumOptions((fns) => { 695 | const suite = describe({ name: "example" }); 696 | assert(suite && typeof suite.symbol === "symbol"); 697 | assertEquals(it({ suite, name: "a", fn: fns[0] }), undefined); 698 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 699 | }), 700 | ); 701 | 702 | await t.step("all options", async () => 703 | await assertAllOptions((fns) => { 704 | const suite = describe({ 705 | name: "example", 706 | fn: () => { 707 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 708 | }, 709 | ...baseOptions, 710 | }); 711 | assert(suite && typeof suite.symbol === "symbol"); 712 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 713 | })); 714 | }); 715 | 716 | await t.step( 717 | "signature 2", 718 | async () => 719 | await assertMinimumOptions((fns) => { 720 | const suite = describe("example"); 721 | assert(suite && typeof suite.symbol === "symbol"); 722 | assertEquals(it({ suite, name: "a", fn: fns[0] }), undefined); 723 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 724 | }), 725 | ); 726 | 727 | await t.step("signature 3", async (t) => { 728 | await t.step( 729 | "minimum options", 730 | async () => 731 | await assertMinimumOptions((fns) => { 732 | const suite = describe("example", {}); 733 | assert(suite && typeof suite.symbol === "symbol"); 734 | assertEquals(it({ suite, name: "a", fn: fns[0] }), undefined); 735 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 736 | }), 737 | ); 738 | 739 | await t.step("all options", async () => 740 | await assertAllOptions((fns) => { 741 | const suite = describe("example", { 742 | fn: () => { 743 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 744 | }, 745 | ...baseOptions, 746 | }); 747 | assert(suite && typeof suite.symbol === "symbol"); 748 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 749 | })); 750 | }); 751 | 752 | await t.step( 753 | "signature 4", 754 | async () => 755 | await assertMinimumOptions((fns) => { 756 | const suite = describe("example", () => { 757 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 758 | }); 759 | assert(suite && typeof suite.symbol === "symbol"); 760 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 761 | }), 762 | ); 763 | 764 | await t.step( 765 | "signature 5", 766 | async () => 767 | await assertMinimumOptions((fns) => { 768 | const suite = describe(function example() { 769 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 770 | }); 771 | assert(suite && typeof suite.symbol === "symbol"); 772 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 773 | }), 774 | ); 775 | 776 | await t.step("signature 6", async (t) => { 777 | await t.step( 778 | "minimum options", 779 | async () => 780 | await assertMinimumOptions((fns) => { 781 | const suite = describe("example", {}, () => { 782 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 783 | }); 784 | assert(suite && typeof suite.symbol === "symbol"); 785 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 786 | }), 787 | ); 788 | 789 | await t.step("all options", async () => 790 | await assertAllOptions((fns) => { 791 | const suite = describe("example", { 792 | ...baseOptions, 793 | }, () => { 794 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 795 | }); 796 | assert(suite && typeof suite.symbol === "symbol"); 797 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 798 | })); 799 | }); 800 | 801 | await t.step("signature 7", async (t) => { 802 | await t.step( 803 | "minimum options", 804 | async () => 805 | await assertMinimumOptions((fns) => { 806 | const suite = describe({ name: "example" }, () => { 807 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 808 | }); 809 | assert(suite && typeof suite.symbol === "symbol"); 810 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 811 | }), 812 | ); 813 | 814 | await t.step("all options", async () => 815 | await assertAllOptions((fns) => { 816 | const suite = describe({ 817 | name: "example", 818 | ...baseOptions, 819 | }, () => { 820 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 821 | }); 822 | assert(suite && typeof suite.symbol === "symbol"); 823 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 824 | })); 825 | }); 826 | 827 | await t.step("signature 8", async (t) => { 828 | await t.step( 829 | "minimum options", 830 | async () => 831 | await assertMinimumOptions((fns) => { 832 | const suite = describe({}, function example() { 833 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 834 | }); 835 | assert(suite && typeof suite.symbol === "symbol"); 836 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 837 | }), 838 | ); 839 | 840 | await t.step("all options", async () => 841 | await assertAllOptions((fns) => { 842 | const suite = describe({ 843 | ...baseOptions, 844 | }, function example() { 845 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 846 | }); 847 | assert(suite && typeof suite.symbol === "symbol"); 848 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 849 | })); 850 | }); 851 | 852 | await t.step("only", async (t) => { 853 | async function assertMinimumOptions( 854 | cb: (fns: Spy[]) => void, 855 | ): Promise { 856 | await assertOptions({ only: true }, cb); 857 | } 858 | 859 | async function assertAllOptions( 860 | cb: (fns: Spy[]) => void, 861 | ): Promise { 862 | await assertOptions({ ...baseOptions, only: true }, cb); 863 | } 864 | 865 | await t.step("signature 1", async (t) => { 866 | await t.step( 867 | "minimum options", 868 | async () => 869 | await assertMinimumOptions((fns) => { 870 | const suite = describe.only({ name: "example" }); 871 | assert(suite && typeof suite.symbol === "symbol"); 872 | assertEquals(it({ suite, name: "a", fn: fns[0] }), undefined); 873 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 874 | }), 875 | ); 876 | 877 | await t.step( 878 | "all options", 879 | async () => 880 | await assertAllOptions((fns) => { 881 | const suite = describe.only({ 882 | name: "example", 883 | fn: () => { 884 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 885 | }, 886 | ...baseOptions, 887 | }); 888 | assert(suite && typeof suite.symbol === "symbol"); 889 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 890 | }), 891 | ); 892 | }); 893 | 894 | await t.step( 895 | "signature 2", 896 | async () => 897 | await assertMinimumOptions((fns) => { 898 | const suite = describe.only("example"); 899 | assert(suite && typeof suite.symbol === "symbol"); 900 | assertEquals(it({ suite, name: "a", fn: fns[0] }), undefined); 901 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 902 | }), 903 | ); 904 | 905 | await t.step("signature 3", async (t) => { 906 | await t.step( 907 | "minimum options", 908 | async () => 909 | await assertMinimumOptions((fns) => { 910 | const suite = describe.only("example", {}); 911 | assert(suite && typeof suite.symbol === "symbol"); 912 | assertEquals(it({ suite, name: "a", fn: fns[0] }), undefined); 913 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 914 | }), 915 | ); 916 | 917 | await t.step( 918 | "all options", 919 | async () => 920 | await assertAllOptions((fns) => { 921 | const suite = describe.only("example", { 922 | fn: () => { 923 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 924 | }, 925 | ...baseOptions, 926 | }); 927 | assert(suite && typeof suite.symbol === "symbol"); 928 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 929 | }), 930 | ); 931 | }); 932 | 933 | await t.step( 934 | "signature 4", 935 | async () => 936 | await assertMinimumOptions((fns) => { 937 | const suite = describe.only("example", () => { 938 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 939 | }); 940 | assert(suite && typeof suite.symbol === "symbol"); 941 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 942 | }), 943 | ); 944 | 945 | await t.step( 946 | "signature 5", 947 | async () => 948 | await assertMinimumOptions((fns) => { 949 | const suite = describe.only(function example() { 950 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 951 | }); 952 | assert(suite && typeof suite.symbol === "symbol"); 953 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 954 | }), 955 | ); 956 | 957 | await t.step("signature 6", async (t) => { 958 | await t.step( 959 | "minimum options", 960 | async () => 961 | await assertMinimumOptions((fns) => { 962 | const suite = describe.only("example", {}, () => { 963 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 964 | }); 965 | assert(suite && typeof suite.symbol === "symbol"); 966 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 967 | }), 968 | ); 969 | 970 | await t.step( 971 | "all options", 972 | async () => 973 | await assertAllOptions((fns) => { 974 | const suite = describe.only("example", { 975 | ...baseOptions, 976 | }, () => { 977 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 978 | }); 979 | assert(suite && typeof suite.symbol === "symbol"); 980 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 981 | }), 982 | ); 983 | }); 984 | 985 | await t.step("signature 7", async (t) => { 986 | await t.step( 987 | "minimum options", 988 | async () => 989 | await assertMinimumOptions((fns) => { 990 | const suite = describe.only({ name: "example" }, () => { 991 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 992 | }); 993 | assert(suite && typeof suite.symbol === "symbol"); 994 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 995 | }), 996 | ); 997 | 998 | await t.step( 999 | "all options", 1000 | async () => 1001 | await assertAllOptions((fns) => { 1002 | const suite = describe.only({ 1003 | name: "example", 1004 | ...baseOptions, 1005 | }, () => { 1006 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1007 | }); 1008 | assert(suite && typeof suite.symbol === "symbol"); 1009 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1010 | }), 1011 | ); 1012 | }); 1013 | 1014 | await t.step("signature 8", async (t) => { 1015 | await t.step( 1016 | "minimum options", 1017 | async () => 1018 | await assertMinimumOptions((fns) => { 1019 | const suite = describe.only({}, function example() { 1020 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1021 | }); 1022 | assert(suite && typeof suite.symbol === "symbol"); 1023 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1024 | }), 1025 | ); 1026 | 1027 | await t.step( 1028 | "all options", 1029 | async () => 1030 | await assertAllOptions((fns) => { 1031 | const suite = describe.only({ 1032 | ...baseOptions, 1033 | }, function example() { 1034 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1035 | }); 1036 | assert(suite && typeof suite.symbol === "symbol"); 1037 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1038 | }), 1039 | ); 1040 | }); 1041 | }); 1042 | 1043 | await t.step("ignore", async (t) => { 1044 | async function assertMinimumOptions( 1045 | cb: (fns: Spy[]) => void, 1046 | ): Promise { 1047 | await assertOptions({ ignore: true }, cb); 1048 | } 1049 | 1050 | async function assertAllOptions( 1051 | cb: (fns: Spy[]) => void, 1052 | ): Promise { 1053 | await assertOptions({ ...baseOptions, ignore: true }, cb); 1054 | } 1055 | 1056 | await t.step("signature 1", async (t) => { 1057 | await t.step( 1058 | "minimum options", 1059 | async () => 1060 | await assertMinimumOptions((fns) => { 1061 | const suite = describe.ignore({ name: "example" }); 1062 | assert(suite && typeof suite.symbol === "symbol"); 1063 | assertEquals(it({ suite, name: "a", fn: fns[0] }), undefined); 1064 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1065 | }), 1066 | ); 1067 | 1068 | await t.step( 1069 | "all options", 1070 | async () => 1071 | await assertAllOptions((fns) => { 1072 | const suite = describe.ignore({ 1073 | name: "example", 1074 | fn: () => { 1075 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1076 | }, 1077 | ...baseOptions, 1078 | }); 1079 | assert(suite && typeof suite.symbol === "symbol"); 1080 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1081 | }), 1082 | ); 1083 | }); 1084 | 1085 | await t.step( 1086 | "signature 2", 1087 | async () => 1088 | await assertMinimumOptions((fns) => { 1089 | const suite = describe.ignore("example"); 1090 | assert(suite && typeof suite.symbol === "symbol"); 1091 | assertEquals(it({ suite, name: "a", fn: fns[0] }), undefined); 1092 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1093 | }), 1094 | ); 1095 | 1096 | await t.step("signature 3", async (t) => { 1097 | await t.step( 1098 | "minimum options", 1099 | async () => 1100 | await assertMinimumOptions((fns) => { 1101 | const suite = describe.ignore("example", {}); 1102 | assert(suite && typeof suite.symbol === "symbol"); 1103 | assertEquals(it({ suite, name: "a", fn: fns[0] }), undefined); 1104 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1105 | }), 1106 | ); 1107 | 1108 | await t.step( 1109 | "all options", 1110 | async () => 1111 | await assertAllOptions((fns) => { 1112 | const suite = describe.ignore("example", { 1113 | fn: () => { 1114 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1115 | }, 1116 | ...baseOptions, 1117 | }); 1118 | assert(suite && typeof suite.symbol === "symbol"); 1119 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1120 | }), 1121 | ); 1122 | }); 1123 | 1124 | await t.step( 1125 | "signature 4", 1126 | async () => 1127 | await assertMinimumOptions((fns) => { 1128 | const suite = describe.ignore("example", () => { 1129 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1130 | }); 1131 | assert(suite && typeof suite.symbol === "symbol"); 1132 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1133 | }), 1134 | ); 1135 | 1136 | await t.step( 1137 | "signature 5", 1138 | async () => 1139 | await assertMinimumOptions((fns) => { 1140 | const suite = describe.ignore(function example() { 1141 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1142 | }); 1143 | assert(suite && typeof suite.symbol === "symbol"); 1144 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1145 | }), 1146 | ); 1147 | 1148 | await t.step("signature 6", async (t) => { 1149 | await t.step( 1150 | "minimum options", 1151 | async () => 1152 | await assertMinimumOptions((fns) => { 1153 | const suite = describe.ignore("example", {}, () => { 1154 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1155 | }); 1156 | assert(suite && typeof suite.symbol === "symbol"); 1157 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1158 | }), 1159 | ); 1160 | 1161 | await t.step( 1162 | "all options", 1163 | async () => 1164 | await assertAllOptions((fns) => { 1165 | const suite = describe.ignore("example", { 1166 | ...baseOptions, 1167 | }, () => { 1168 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1169 | }); 1170 | assert(suite && typeof suite.symbol === "symbol"); 1171 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1172 | }), 1173 | ); 1174 | }); 1175 | 1176 | await t.step("signature 7", async (t) => { 1177 | await t.step( 1178 | "minimum options", 1179 | async () => 1180 | await assertMinimumOptions((fns) => { 1181 | const suite = describe.ignore({ name: "example" }, () => { 1182 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1183 | }); 1184 | assert(suite && typeof suite.symbol === "symbol"); 1185 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1186 | }), 1187 | ); 1188 | 1189 | await t.step( 1190 | "all options", 1191 | async () => 1192 | await assertAllOptions((fns) => { 1193 | const suite = describe.ignore({ 1194 | name: "example", 1195 | ...baseOptions, 1196 | }, () => { 1197 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1198 | }); 1199 | assert(suite && typeof suite.symbol === "symbol"); 1200 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1201 | }), 1202 | ); 1203 | }); 1204 | 1205 | await t.step("signature 8", async (t) => { 1206 | await t.step( 1207 | "minimum options", 1208 | async () => 1209 | await assertMinimumOptions((fns) => { 1210 | const suite = describe.ignore({}, function example() { 1211 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1212 | }); 1213 | assert(suite && typeof suite.symbol === "symbol"); 1214 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1215 | }), 1216 | ); 1217 | 1218 | await t.step( 1219 | "all options", 1220 | async () => 1221 | await assertAllOptions((fns) => { 1222 | const suite = describe.ignore({ 1223 | ...baseOptions, 1224 | }, function example() { 1225 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1226 | }); 1227 | assert(suite && typeof suite.symbol === "symbol"); 1228 | assertEquals(it({ suite, name: "b", fn: fns[1] }), undefined); 1229 | }), 1230 | ); 1231 | }); 1232 | }); 1233 | 1234 | await t.step("nested only", async (t) => { 1235 | async function assertOnly( 1236 | cb: (fns: Spy[]) => void, 1237 | ): Promise { 1238 | const test = stub(Deno, "test"); 1239 | const fns = [spy(), spy(), spy()]; 1240 | try { 1241 | cb(fns); 1242 | 1243 | assertSpyCall(test, 0); 1244 | const call = test.calls[0]; 1245 | const options = call.args[0] as Deno.TestDefinition; 1246 | assertEquals( 1247 | Object.keys(options).sort(), 1248 | ["name", "fn"].sort(), 1249 | ); 1250 | assertObjectMatch(options, { 1251 | name: "example", 1252 | }); 1253 | 1254 | assertSpyCalls(fns[0], 0); 1255 | assertSpyCalls(fns[1], 0); 1256 | 1257 | const context = new TestContext(); 1258 | const result = options.fn(context); 1259 | assertStrictEquals(Promise.resolve(result), result); 1260 | assertEquals(await result, undefined); 1261 | assertSpyCalls(context.spies.step, 1); 1262 | 1263 | let fn = fns[0]; 1264 | assertSpyCalls(fn, 0); 1265 | 1266 | fn = fns[1]; 1267 | assertSpyCall(fn, 0, { 1268 | self: {}, 1269 | args: [], 1270 | returned: undefined, 1271 | }); 1272 | assertSpyCalls(fn, 1); 1273 | 1274 | fn = fns[2]; 1275 | assertSpyCalls(fn, 0); 1276 | } finally { 1277 | TestSuiteInternal.reset(); 1278 | test.restore(); 1279 | } 1280 | } 1281 | 1282 | await t.step("it", async () => 1283 | await assertOnly((fns) => { 1284 | describe("example", () => { 1285 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1286 | assertEquals(it.only({ name: "b", fn: fns[1] }), undefined); 1287 | assertEquals(it({ name: "c", fn: fns[2] }), undefined); 1288 | }); 1289 | })); 1290 | 1291 | await t.step("nested it", async () => 1292 | await assertOnly((fns) => { 1293 | describe("example", () => { 1294 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1295 | describe("nested", () => { 1296 | assertEquals(it.only({ name: "b", fn: fns[1] }), undefined); 1297 | }); 1298 | assertEquals(it({ name: "c", fn: fns[2] }), undefined); 1299 | }); 1300 | })); 1301 | 1302 | await t.step("describe", async () => 1303 | await assertOnly((fns) => { 1304 | describe("example", () => { 1305 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1306 | describe.only("nested", () => { 1307 | assertEquals(it({ name: "b", fn: fns[1] }), undefined); 1308 | }); 1309 | assertEquals(it({ name: "c", fn: fns[2] }), undefined); 1310 | }); 1311 | })); 1312 | 1313 | await t.step("nested describe", async () => 1314 | await assertOnly((fns) => { 1315 | describe("example", () => { 1316 | assertEquals(it({ name: "a", fn: fns[0] }), undefined); 1317 | describe("nested", () => { 1318 | describe.only("nested 2", () => { 1319 | assertEquals(it({ name: "b", fn: fns[1] }), undefined); 1320 | }); 1321 | }); 1322 | assertEquals(it({ name: "c", fn: fns[2] }), undefined); 1323 | }); 1324 | })); 1325 | }); 1326 | 1327 | await t.step("with hooks", async (t) => { 1328 | async function assertHooks( 1329 | cb: ( 1330 | options: { 1331 | beforeAllFn: Spy; 1332 | afterAllFn: Spy; 1333 | beforeEachFn: Spy; 1334 | afterEachFn: Spy; 1335 | fns: Spy[]; 1336 | }, 1337 | ) => void, 1338 | ) { 1339 | const test = stub(Deno, "test"), 1340 | fns = [spy(), spy()], 1341 | { beforeAllFn, afterAllFn, beforeEachFn, afterEachFn } = hookFns(); 1342 | 1343 | try { 1344 | cb({ beforeAllFn, afterAllFn, beforeEachFn, afterEachFn, fns }); 1345 | 1346 | assertSpyCalls(fns[0], 0); 1347 | assertSpyCalls(fns[1], 0); 1348 | 1349 | assertSpyCall(test, 0); 1350 | const call = test.calls[0]; 1351 | const options = call.args[0] as Deno.TestDefinition; 1352 | assertEquals(Object.keys(options).sort(), ["fn", "name"]); 1353 | assertEquals(options.name, "example"); 1354 | 1355 | const context = new TestContext(); 1356 | const result = options.fn(context); 1357 | assertStrictEquals(Promise.resolve(result), result); 1358 | assertEquals(await result, undefined); 1359 | assertSpyCalls(context.spies.step, 2); 1360 | } finally { 1361 | TestSuiteInternal.reset(); 1362 | test.restore(); 1363 | } 1364 | 1365 | let fn = fns[0]; 1366 | assertSpyCall(fn, 0, { 1367 | self: { allTimer: 1, eachTimer: 2 }, 1368 | args: [], 1369 | returned: undefined, 1370 | }); 1371 | assertSpyCalls(fn, 1); 1372 | 1373 | fn = fns[1]; 1374 | assertSpyCall(fn, 0, { 1375 | self: { allTimer: 1, eachTimer: 3 }, 1376 | args: [], 1377 | returned: undefined, 1378 | }); 1379 | assertSpyCalls(fn, 1); 1380 | 1381 | assertSpyCalls(beforeAllFn, 1); 1382 | assertSpyCalls(afterAllFn, 1); 1383 | assertSpyCalls(beforeEachFn, 2); 1384 | assertSpyCalls(afterEachFn, 2); 1385 | } 1386 | 1387 | await t.step( 1388 | "in callback", 1389 | async () => 1390 | await assertHooks( 1391 | ({ beforeAllFn, afterAllFn, beforeEachFn, afterEachFn, fns }) => { 1392 | describe("example", () => { 1393 | beforeAll(beforeAllFn); 1394 | afterAll(afterAllFn); 1395 | 1396 | beforeEach(beforeEachFn); 1397 | afterEach(afterEachFn); 1398 | 1399 | assertEquals(it({ name: "example 1", fn: fns[0] }), undefined); 1400 | assertEquals(it({ name: "example 2", fn: fns[1] }), undefined); 1401 | }); 1402 | }, 1403 | ), 1404 | ); 1405 | 1406 | await t.step( 1407 | "in options", 1408 | async () => 1409 | await assertHooks( 1410 | ({ beforeAllFn, afterAllFn, beforeEachFn, afterEachFn, fns }) => { 1411 | describe({ 1412 | name: "example", 1413 | beforeAll: beforeAllFn, 1414 | afterAll: afterAllFn, 1415 | beforeEach: beforeEachFn, 1416 | afterEach: afterEachFn, 1417 | fn: () => { 1418 | assertEquals( 1419 | it({ name: "example 1", fn: fns[0] }), 1420 | undefined, 1421 | ); 1422 | assertEquals( 1423 | it({ name: "example 2", fn: fns[1] }), 1424 | undefined, 1425 | ); 1426 | }, 1427 | }); 1428 | }, 1429 | ), 1430 | ); 1431 | 1432 | await t.step( 1433 | "nested", 1434 | async () => { 1435 | const test = stub(Deno, "test"), 1436 | fns = [spy(), spy()], 1437 | { beforeAllFn, afterAllFn, beforeEachFn, afterEachFn } = hookFns(); 1438 | 1439 | try { 1440 | describe("example", () => { 1441 | beforeAll(beforeAllFn); 1442 | afterAll(afterAllFn); 1443 | 1444 | beforeEach(beforeEachFn); 1445 | afterEach(afterEachFn); 1446 | 1447 | describe("nested", () => { 1448 | assertEquals(it({ name: "example 1", fn: fns[0] }), undefined); 1449 | assertEquals(it({ name: "example 2", fn: fns[1] }), undefined); 1450 | }); 1451 | }); 1452 | 1453 | assertSpyCalls(fns[0], 0); 1454 | assertSpyCalls(fns[1], 0); 1455 | 1456 | assertSpyCall(test, 0); 1457 | const call = test.calls[0]; 1458 | const options = call.args[0] as Deno.TestDefinition; 1459 | assertEquals(Object.keys(options).sort(), ["fn", "name"]); 1460 | assertEquals(options.name, "example"); 1461 | 1462 | let context = new TestContext(); 1463 | const result = options.fn(context); 1464 | assertStrictEquals(Promise.resolve(result), result); 1465 | assertEquals(await result, undefined); 1466 | assertSpyCalls(context.spies.step, 1); 1467 | 1468 | context = context.steps[0]; 1469 | assertStrictEquals(Promise.resolve(result), result); 1470 | assertEquals(await result, undefined); 1471 | assertSpyCalls(context.spies.step, 2); 1472 | } finally { 1473 | TestSuiteInternal.reset(); 1474 | test.restore(); 1475 | } 1476 | 1477 | let fn = fns[0]; 1478 | assertSpyCall(fn, 0, { 1479 | self: { allTimer: 1, eachTimer: 2 }, 1480 | args: [], 1481 | returned: undefined, 1482 | }); 1483 | assertSpyCalls(fn, 1); 1484 | 1485 | fn = fns[1]; 1486 | assertSpyCall(fn, 0, { 1487 | self: { allTimer: 1, eachTimer: 3 }, 1488 | args: [], 1489 | returned: undefined, 1490 | }); 1491 | assertSpyCalls(fn, 1); 1492 | 1493 | assertSpyCalls(beforeAllFn, 1); 1494 | assertSpyCalls(afterAllFn, 1); 1495 | assertSpyCalls(beforeEachFn, 2); 1496 | assertSpyCalls(afterEachFn, 2); 1497 | }, 1498 | ); 1499 | 1500 | interface NestedContext extends GlobalContext { 1501 | allTimerNested: number; 1502 | eachTimerNested: number; 1503 | } 1504 | 1505 | await t.step( 1506 | "nested with hooks", 1507 | async () => { 1508 | const test = stub(Deno, "test"), 1509 | fns = [spy(), spy()], 1510 | { beforeAllFn, afterAllFn, beforeEachFn, afterEachFn } = hookFns(), 1511 | beforeAllFnNested = spy(async function (this: NestedContext) { 1512 | await Promise.resolve(); 1513 | this.allTimerNested = timerIdx++; 1514 | timers.set( 1515 | this.allTimerNested, 1516 | setTimeout(() => {}, 10000), 1517 | ); 1518 | }), 1519 | afterAllFnNested = spy( 1520 | async function (this: NestedContext) { 1521 | await Promise.resolve(); 1522 | clearTimeout(timers.get(this.allTimerNested)); 1523 | }, 1524 | ), 1525 | beforeEachFnNested = spy(async function (this: NestedContext) { 1526 | await Promise.resolve(); 1527 | this.eachTimerNested = timerIdx++; 1528 | timers.set( 1529 | this.eachTimerNested, 1530 | setTimeout(() => {}, 10000), 1531 | ); 1532 | }), 1533 | afterEachFnNested = spy( 1534 | async function (this: NestedContext) { 1535 | await Promise.resolve(); 1536 | clearTimeout(timers.get(this.eachTimerNested)); 1537 | }, 1538 | ); 1539 | 1540 | try { 1541 | describe("example", () => { 1542 | beforeAll(beforeAllFn); 1543 | afterAll(afterAllFn); 1544 | 1545 | beforeEach(beforeEachFn); 1546 | afterEach(afterEachFn); 1547 | 1548 | describe("nested", () => { 1549 | beforeAll(beforeAllFnNested); 1550 | afterAll(afterAllFnNested); 1551 | 1552 | beforeEach(beforeEachFnNested); 1553 | afterEach(afterEachFnNested); 1554 | 1555 | assertEquals(it({ name: "example 1", fn: fns[0] }), undefined); 1556 | assertEquals(it({ name: "example 2", fn: fns[1] }), undefined); 1557 | }); 1558 | }); 1559 | 1560 | assertSpyCalls(fns[0], 0); 1561 | assertSpyCalls(fns[1], 0); 1562 | 1563 | assertSpyCall(test, 0); 1564 | const call = test.calls[0]; 1565 | const options = call.args[0] as Deno.TestDefinition; 1566 | assertEquals(Object.keys(options).sort(), ["fn", "name"]); 1567 | assertEquals(options.name, "example"); 1568 | 1569 | let context = new TestContext(); 1570 | const result = options.fn(context); 1571 | assertStrictEquals(Promise.resolve(result), result); 1572 | assertEquals(await result, undefined); 1573 | assertSpyCalls(context.spies.step, 1); 1574 | 1575 | context = context.steps[0]; 1576 | assertStrictEquals(Promise.resolve(result), result); 1577 | assertEquals(await result, undefined); 1578 | assertSpyCalls(context.spies.step, 2); 1579 | } finally { 1580 | TestSuiteInternal.reset(); 1581 | test.restore(); 1582 | } 1583 | 1584 | let fn = fns[0]; 1585 | assertSpyCall(fn, 0, { 1586 | self: { 1587 | allTimer: 1, 1588 | allTimerNested: 2, 1589 | eachTimer: 3, 1590 | eachTimerNested: 4, 1591 | }, 1592 | args: [], 1593 | returned: undefined, 1594 | }); 1595 | assertSpyCalls(fn, 1); 1596 | 1597 | fn = fns[1]; 1598 | assertSpyCall(fn, 0, { 1599 | self: { 1600 | allTimer: 1, 1601 | allTimerNested: 2, 1602 | eachTimer: 5, 1603 | eachTimerNested: 6, 1604 | }, 1605 | args: [], 1606 | returned: undefined, 1607 | }); 1608 | assertSpyCalls(fn, 1); 1609 | 1610 | assertSpyCalls(beforeAllFn, 1); 1611 | assertSpyCalls(afterAllFn, 1); 1612 | assertSpyCalls(beforeEachFn, 2); 1613 | assertSpyCalls(afterEachFn, 2); 1614 | 1615 | assertSpyCalls(beforeAllFnNested, 1); 1616 | assertSpyCalls(afterAllFnNested, 1); 1617 | assertSpyCalls(beforeEachFnNested, 2); 1618 | assertSpyCalls(afterEachFnNested, 2); 1619 | }, 1620 | ); 1621 | }); 1622 | }); 1623 | }); 1624 | --------------------------------------------------------------------------------