From d8afd5683857de83f3cc80c33322df3d65377210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 23 Nov 2021 14:57:51 +0100 Subject: [PATCH] feat(test): Add more overloads for "Deno.test" (#12749) This commit adds 4 more overloads to "Deno.test()" API. ``` // Deno.test(function testName() { }); export function test(fn: (t: TestContext) => void | Promise): void; // Deno.test("test name", { only: true }, function() { }); export function test( name: string, options: Omit, fn: (t: TestContext) => void | Promise, ): void; // Deno.test({ name: "test name" }, function() { }); export function test( options: Omit, fn: (t: TestContext) => void | Promise, ): void; // Deno.test({ only: true }, function testName() { }); export function test( options: Omit, fn: (t: TestContext) => void | Promise, ): void; ``` --- cli/dts/lib.deno.ns.d.ts | 93 ++++++++++++++++++++++++++- cli/tests/integration/mod.rs | 20 +++--- cli/tests/integration/test_tests.rs | 6 ++ cli/tests/testdata/test/overloads.out | 11 ++++ cli/tests/testdata/test/overloads.ts | 6 ++ cli/tests/unit/testing_test.ts | 60 ++++++++++++++++- runtime/js/40_testing.js | 77 ++++++++++++++++++---- 7 files changed, 246 insertions(+), 27 deletions(-) create mode 100644 cli/tests/testdata/test/overloads.out create mode 100644 cli/tests/testdata/test/overloads.ts diff --git a/cli/dts/lib.deno.ns.d.ts b/cli/dts/lib.deno.ns.d.ts index 74aa334e2b..4bc938ba11 100644 --- a/cli/dts/lib.deno.ns.d.ts +++ b/cli/dts/lib.deno.ns.d.ts @@ -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 => { + * Deno.test("My async test description", async (): Promise => { * const decoder = new TextDecoder("utf-8"); * const data = await Deno.readFile("hello_world.txt"); * assertEquals(decoder.decode(data), "Hello world"); @@ -331,6 +331,95 @@ declare namespace Deno { fn: (t: TestContext) => void | Promise, ): 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 { + * 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; + + /** 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 => { + * 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, + fn: (t: TestContext) => void | Promise, + ): 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 => { + * 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, + fn: (t: TestContext) => void | Promise, + ): 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 { + * 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, + fn: (t: TestContext) => void | Promise, + ): void; /** Exit the Deno process with optional exit code. If no exit code is supplied * then Deno will exit with return code of 0. * diff --git a/cli/tests/integration/mod.rs b/cli/tests/integration/mod.rs index 21ffc56279..cfb9509012 100644 --- a/cli/tests/integration/mod.rs +++ b/cli/tests/integration/mod.rs @@ -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] diff --git a/cli/tests/integration/test_tests.rs b/cli/tests/integration/test_tests.rs index e5d3fd3584..3f23efca22 100644 --- a/cli/tests/integration/test_tests.rs +++ b/cli/tests/integration/test_tests.rs @@ -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, diff --git a/cli/tests/testdata/test/overloads.out b/cli/tests/testdata/test/overloads.out new file mode 100644 index 0000000000..13c088f6c4 --- /dev/null +++ b/cli/tests/testdata/test/overloads.out @@ -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]) + diff --git a/cli/tests/testdata/test/overloads.ts b/cli/tests/testdata/test/overloads.ts new file mode 100644 index 0000000000..eb7b3dcccc --- /dev/null +++ b/cli/tests/testdata/test/overloads.ts @@ -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() {}); diff --git a/cli/tests/unit/testing_test.ts b/cli/tests/unit/testing_test.ts index 144246002a..d4d25d12fe 100644 --- a/cli/tests/unit/testing_test.ts +++ b/cli/tests/unit/testing_test.ts @@ -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() { diff --git a/runtime/js/40_testing.js b/runtime/js/40_testing.js index e07d54a1a6..053afc5dab 100644 --- a/runtime/js/40_testing.js +++ b/runtime/js/40_testing.js @@ -254,8 +254,9 @@ finishing test case.`; // Main test function provided by Deno. function test( - t, - fn, + nameOrFnOrOptions, + optionsOrFn, + maybeFn, ) { let testDef; const defaults = { @@ -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);