diff --git a/cli/js/40_testing.js b/cli/js/40_testing.js index d71609dd19..0693fba1a1 100644 --- a/cli/js/40_testing.js +++ b/cli/js/40_testing.js @@ -1265,10 +1265,10 @@ */ origin: desc.origin, /** - * @param nameOrTestDefinition {string | TestStepDefinition} - * @param fn {(t: TestContext) => void | Promise} + * @param nameOrFnOrOptions {string | TestStepDefinition | ((t: TestContext) => void | Promise)} + * @param maybeFn {((t: TestContext) => void | Promise) | undefined} */ - async step(nameOrTestDefinition, fn) { + async step(nameOrFnOrOptions, maybeFn) { if (MapPrototypeGet(testStates, desc.id).finalized) { throw new Error( "Cannot run test step after parent scope has finished execution. " + @@ -1277,16 +1277,29 @@ } let stepDesc; - if (typeof nameOrTestDefinition === "string") { - if (!(ObjectPrototypeIsPrototypeOf(FunctionPrototype, fn))) { + if (typeof nameOrFnOrOptions === "string") { + if (!(ObjectPrototypeIsPrototypeOf(FunctionPrototype, maybeFn))) { throw new TypeError("Expected function for second argument."); } stepDesc = { - name: nameOrTestDefinition, - fn, + name: nameOrFnOrOptions, + fn: maybeFn, }; - } else if (typeof nameOrTestDefinition === "object") { - stepDesc = nameOrTestDefinition; + } else if (typeof nameOrFnOrOptions === "function") { + if (!nameOrFnOrOptions.name) { + throw new TypeError("The step function must have a name."); + } + if (maybeFn != undefined) { + throw new TypeError( + "Unexpected second argument to TestContext.step()", + ); + } + stepDesc = { + name: nameOrFnOrOptions.name, + fn: nameOrFnOrOptions, + }; + } else if (typeof nameOrFnOrOptions === "object") { + stepDesc = nameOrFnOrOptions; } else { throw new TypeError( "Expected a test definition or name and function.", diff --git a/cli/tests/testdata/test/steps/passing_steps.out b/cli/tests/testdata/test/steps/passing_steps.out index 5eacc571cb..231a9a1708 100644 --- a/cli/tests/testdata/test/steps/passing_steps.out +++ b/cli/tests/testdata/test/steps/passing_steps.out @@ -1,11 +1,17 @@ [WILDCARD] -running 5 tests from ./test/steps/passing_steps.ts +running 6 tests from ./test/steps/passing_steps.ts description ... step 1 ... inner 1 ... ok ([WILDCARD]ms) inner 2 ... ok ([WILDCARD]ms) step 1 ... ok ([WILDCARD]ms) description ... ok ([WILDCARD]ms) +description function as first arg ... + step1 ... + inner1 ... ok ([WILDCARD]ms) + inner1 ... ok ([WILDCARD]ms) + step1 ... ok ([WILDCARD]ms) +description function as first arg ... ok ([WILDCARD]ms) parallel steps without sanitizers ... step 1 ... ok ([WILDCARD]) step 2 ... ok ([WILDCARD]) @@ -35,4 +41,4 @@ steps buffered then streaming reporting ... step 2 ... ok ([WILDCARD]) steps buffered then streaming reporting ... ok ([WILDCARD]) -ok | 5 passed (18 steps) | 0 failed [WILDCARD] +ok | 6 passed (21 steps) | 0 failed [WILDCARD] diff --git a/cli/tests/testdata/test/steps/passing_steps.ts b/cli/tests/testdata/test/steps/passing_steps.ts index fbd52e2d30..38de116a72 100644 --- a/cli/tests/testdata/test/steps/passing_steps.ts +++ b/cli/tests/testdata/test/steps/passing_steps.ts @@ -9,6 +9,15 @@ Deno.test("description", async (t) => { if (!success) throw new Error("Expected the step to return true."); }); +Deno.test("description function as first arg", async (t) => { + const success = await t.step(async function step1(t) { + await t.step(function inner1() {}); + await t.step(function inner1() {}); + }); + + if (!success) throw new Error("Expected the step to return true."); +}); + Deno.test("parallel steps without sanitizers", async (t) => { // allowed await Promise.all([ diff --git a/cli/tests/unit/testing_test.ts b/cli/tests/unit/testing_test.ts index 9c906a1112..4e28d545c5 100644 --- a/cli/tests/unit/testing_test.ts +++ b/cli/tests/unit/testing_test.ts @@ -114,7 +114,7 @@ Deno.test(async function invalidStepArguments(t) { await (t as any).step(() => {}); }, TypeError, - "Expected a test definition or name and function.", + "The step function must have a name.", ); }); diff --git a/cli/tsc/dts/lib.deno.ns.d.ts b/cli/tsc/dts/lib.deno.ns.d.ts index 7473b14151..b6add6e4bb 100644 --- a/cli/tsc/dts/lib.deno.ns.d.ts +++ b/cli/tsc/dts/lib.deno.ns.d.ts @@ -668,6 +668,25 @@ declare namespace Deno { name: string, fn: (t: TestContext) => void | Promise, ): Promise; + + /** Run a sub step of the parent test or step. Returns a promise + * that resolves to a boolean signifying if the step completed successfully. + * + * The returned promise never rejects unless the arguments are invalid. + * + * If the test was ignored the promise returns `false`. + * + * ```ts + * Deno.test(async function aParentTest(t) { + * console.log("before the step"); + * await t.step(function step1(t) { + * console.log("current step:", t.name); + * }); + * console.log("after the step"); + * }); + * ``` + */ + step(fn: (t: TestContext) => void | Promise): Promise; } /** @category Testing */