Skip to content

Commit

Permalink
feat(test): Add more overloads for "Deno.test" (denoland#12749)
Browse files Browse the repository at this point in the history
This commit adds 4 more overloads to "Deno.test()" API.

```
// Deno.test(function testName() { });
export function test(fn: (t: TestContext) => void | Promise<void>): void;

// Deno.test("test name", { only: true }, function() { });
export function test(
  name: string,
  options: Omit<TestDefinition, "name">,
  fn: (t: TestContext) => void | Promise<void>,
): void;

// Deno.test({ name: "test name" }, function() { });
export function test(
  options: Omit<TestDefinition, "fn">,
  fn: (t: TestContext) => void | Promise<void>,
): void;

// Deno.test({ only: true }, function testName() { });
export function test(
  options: Omit<TestDefinition, "fn" | "name">,
  fn: (t: TestContext) => void | Promise<void>,
): void;
```
  • Loading branch information
bartlomieju authored Nov 23, 2021
1 parent ae34f8f commit d8afd56
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 27 deletions.
93 changes: 91 additions & 2 deletions cli/dts/lib.deno.ns.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,11 +315,11 @@ declare namespace Deno {
* ```ts
* import {assert, fail, assertEquals} from "https://deno.land/std/testing/asserts.ts";
*
* Deno.test("My test description", ():void => {
* Deno.test("My test description", (): void => {
* assertEquals("hello", "hello");
* });
*
* Deno.test("My async test description", async ():Promise<void> => {
* Deno.test("My async test description", async (): Promise<void> => {
* const decoder = new TextDecoder("utf-8");
* const data = await Deno.readFile("hello_world.txt");
* assertEquals(decoder.decode(data), "Hello world");
Expand All @@ -331,6 +331,95 @@ declare namespace Deno {
fn: (t: TestContext) => void | Promise<void>,
): void;

/** Register a test which will be run when `deno test` is used on the command
* line and the containing module looks like a test module.
* `fn` can be async if required. Declared function must have a name.
*
* ```ts
* import {assert, fail, assertEquals} from "https://deno.land/std/testing/asserts.ts";
*
* Deno.test(function myTestName(): void {
* assertEquals("hello", "hello");
* });
*
* Deno.test(async function myOtherTestName(): Promise<void> {
* const decoder = new TextDecoder("utf-8");
* const data = await Deno.readFile("hello_world.txt");
* assertEquals(decoder.decode(data), "Hello world");
* });
* ```
*/
export function test(fn: (t: TestContext) => void | Promise<void>): void;

/** Register a test which will be run when `deno test` is used on the command
* line and the containing module looks like a test module.
* `fn` can be async if required.
*
* ```ts
* import {assert, fail, assertEquals} from "https://deno.land/std/testing/asserts.ts";
*
* Deno.test("My test description", { permissions: { read: true } }, (): void => {
* assertEquals("hello", "hello");
* });
*
* Deno.test("My async test description", { permissions: { read: false } }, async (): Promise<void> => {
* const decoder = new TextDecoder("utf-8");
* const data = await Deno.readFile("hello_world.txt");
* assertEquals(decoder.decode(data), "Hello world");
* });
* ```
*/
export function test(
name: string,
options: Omit<TestDefinition, "fn" | "name">,
fn: (t: TestContext) => void | Promise<void>,
): void;

/** Register a test which will be run when `deno test` is used on the command
* line and the containing module looks like a test module.
* `fn` can be async if required.
*
* ```ts
* import {assert, fail, assertEquals} from "https://deno.land/std/testing/asserts.ts";
*
* Deno.test({ name: "My test description", permissions: { read: true } }, (): void => {
* assertEquals("hello", "hello");
* });
*
* Deno.test({ name: "My async test description", permissions: { read: false } }, async (): Promise<void> => {
* const decoder = new TextDecoder("utf-8");
* const data = await Deno.readFile("hello_world.txt");
* assertEquals(decoder.decode(data), "Hello world");
* });
* ```
*/
export function test(
options: Omit<TestDefinition, "fn">,
fn: (t: TestContext) => void | Promise<void>,
): void;

/** Register a test which will be run when `deno test` is used on the command
* line and the containing module looks like a test module.
* `fn` can be async if required. Declared function must have a name.
*
* ```ts
* import {assert, fail, assertEquals} from "https://deno.land/std/testing/asserts.ts";
*
* Deno.test({ permissions: { read: true } }, function myTestName(): void {
* assertEquals("hello", "hello");
* });
*
* Deno.test({ permissions: { read: false } }, async function myOtherTestName(): Promise<void> {
* const decoder = new TextDecoder("utf-8");
* const data = await Deno.readFile("hello_world.txt");
* assertEquals(decoder.decode(data), "Hello world");
* });
* ```
*/
export function test(
options: Omit<TestDefinition, "fn" | "name">,
fn: (t: TestContext) => void | Promise<void>,
): void;
/** Exit the Deno process with optional exit code. If no exit code is supplied
* then Deno will exit with return code of 0.
*
Expand Down
20 changes: 10 additions & 10 deletions cli/tests/integration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1016,29 +1016,29 @@ async fn test_resolve_dns() {

#[test]
fn typecheck_declarations_ns() {
let status = util::deno_cmd()
let output = util::deno_cmd()
.arg("test")
.arg("--doc")
.arg(util::root_path().join("cli/dts/lib.deno.ns.d.ts"))
.spawn()
.unwrap()
.wait()
.output()
.unwrap();
assert!(status.success());
println!("stdout: {}", String::from_utf8(output.stdout).unwrap());
println!("stderr: {}", String::from_utf8(output.stderr).unwrap());
assert!(output.status.success());
}

#[test]
fn typecheck_declarations_unstable() {
let status = util::deno_cmd()
let output = util::deno_cmd()
.arg("test")
.arg("--doc")
.arg("--unstable")
.arg(util::root_path().join("cli/dts/lib.deno.unstable.d.ts"))
.spawn()
.unwrap()
.wait()
.output()
.unwrap();
assert!(status.success());
println!("stdout: {}", String::from_utf8(output.stdout).unwrap());
println!("stderr: {}", String::from_utf8(output.stderr).unwrap());
assert!(output.status.success());
}

#[test]
Expand Down
6 changes: 6 additions & 0 deletions cli/tests/integration/test_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ fn no_color() {
assert!(out.contains("test result: FAILED. 1 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out"));
}

itest!(overloads {
args: "test test/overloads.ts",
exit_code: 0,
output: "test/overloads.out",
});

itest!(meta {
args: "test test/meta.ts",
exit_code: 0,
Expand Down
11 changes: 11 additions & 0 deletions cli/tests/testdata/test/overloads.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Check [WILDCARD]/test/overloads.ts
running 6 tests from [WILDCARD]/test/overloads.ts
test test0 ... ok ([WILDCARD])
test test1 ... ok ([WILDCARD])
test test2 ... ok ([WILDCARD])
test test3 ... ok ([WILDCARD])
test test4 ... ok ([WILDCARD])
test test5 ... ignored ([WILDCARD])

test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out ([WILDCARD])

6 changes: 6 additions & 0 deletions cli/tests/testdata/test/overloads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Deno.test("test0", () => {});
Deno.test(function test1() {});
Deno.test({ name: "test2", fn: () => {} });
Deno.test("test3", { permissions: "none" }, () => {});
Deno.test({ name: "test4" }, () => {});
Deno.test({ ignore: true }, function test5() {});
60 changes: 57 additions & 3 deletions cli/tests/unit/testing_test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,63 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
import { assertRejects, assertThrows, unitTest } from "./test_util.ts";

unitTest(function testFnOverloading() {
// just verifying that you can use this test definition syntax
Deno.test("test fn overloading", () => {});
unitTest(function testWrongOverloads() {
assertThrows(
() => {
// @ts-ignore Testing invalid overloads
Deno.test("some name", { fn: () => {} }, () => {});
},
TypeError,
"Unexpected 'fn' field in options, test function is already provided as the third argument.",
);
assertThrows(
() => {
// @ts-ignore Testing invalid overloads
Deno.test("some name", { name: "some name2" }, () => {});
},
TypeError,
"Unexpected 'name' field in options, test name is already provided as the first argument.",
);
assertThrows(
() => {
// @ts-ignore Testing invalid overloads
Deno.test(() => {});
},
TypeError,
"The test function must have a name",
);
assertThrows(
() => {
// @ts-ignore Testing invalid overloads
Deno.test(function foo() {}, {});
},
TypeError,
"Unexpected second argument to Deno.test()",
);
assertThrows(
() => {
// @ts-ignore Testing invalid overloads
Deno.test({ fn: () => {} }, function foo() {});
},
TypeError,
"Unexpected 'fn' field in options, test function is already provided as the second argument.",
);
assertThrows(
() => {
// @ts-ignore Testing invalid overloads
Deno.test({});
},
TypeError,
"Expected 'fn' field in the first argument to be a test function.",
);
assertThrows(
() => {
// @ts-ignore Testing invalid overloads
Deno.test({ fn: "boo!" });
},
TypeError,
"Expected 'fn' field in the first argument to be a test function.",
);
});

unitTest(function nameOfTestCaseCantBeEmpty() {
Expand Down
77 changes: 65 additions & 12 deletions runtime/js/40_testing.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,9 @@ finishing test case.`;

// Main test function provided by Deno.
function test(
t,
fn,
nameOrFnOrOptions,
optionsOrFn,
maybeFn,
) {
let testDef;
const defaults = {
Expand All @@ -267,22 +268,74 @@ finishing test case.`;
permissions: null,
};

if (typeof t === "string") {
if (!fn || typeof fn != "function") {
throw new TypeError("Missing test function");
}
if (!t) {
if (typeof nameOrFnOrOptions === "string") {
if (!nameOrFnOrOptions) {
throw new TypeError("The test name can't be empty");
}
testDef = { fn: fn, name: t, ...defaults };
if (typeof optionsOrFn === "function") {
testDef = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults };
} else {
if (!maybeFn || typeof maybeFn !== "function") {
throw new TypeError("Missing test function");
}
if (optionsOrFn.fn != undefined) {
throw new TypeError(
"Unexpected 'fn' field in options, test function is already provided as the third argument.",
);
}
if (optionsOrFn.name != undefined) {
throw new TypeError(
"Unexpected 'name' field in options, test name is already provided as the first argument.",
);
}
testDef = {
...defaults,
...optionsOrFn,
fn: maybeFn,
name: nameOrFnOrOptions,
};
}
} else if (typeof nameOrFnOrOptions === "function") {
if (!nameOrFnOrOptions.name) {
throw new TypeError("The test function must have a name");
}
if (optionsOrFn != undefined) {
throw new TypeError("Unexpected second argument to Deno.test()");
}
if (maybeFn != undefined) {
throw new TypeError("Unexpected third argument to Deno.test()");
}
testDef = {
...defaults,
fn: nameOrFnOrOptions,
name: nameOrFnOrOptions.name,
};
} else {
if (!t.fn) {
throw new TypeError("Missing test function");
let fn;
let name;
if (typeof optionsOrFn === "function") {
fn = optionsOrFn;
if (nameOrFnOrOptions.fn != undefined) {
throw new TypeError(
"Unexpected 'fn' field in options, test function is already provided as the second argument.",
);
}
name = nameOrFnOrOptions.name ?? fn.name;
} else {
if (
!nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function"
) {
throw new TypeError(
"Expected 'fn' field in the first argument to be a test function.",
);
}
fn = nameOrFnOrOptions.fn;
name = nameOrFnOrOptions.name ?? fn.name;
}
if (!t.name) {
if (!name) {
throw new TypeError("The test name can't be empty");
}
testDef = { ...defaults, ...t };
testDef = { ...defaults, ...nameOrFnOrOptions, fn, name };
}

testDef.fn = wrapTestFnWithSanitizers(testDef.fn, testDef);
Expand Down

0 comments on commit d8afd56

Please sign in to comment.