Skip to content

feat(testing/unstable): add it.todo test.todo and describe.todo API #6712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions testing/_test_helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import {
assertEquals,
assertObjectMatch,
assertStrictEquals,
} from "@std/assert";
import { assertSpyCall, assertSpyCalls, type Spy, spy, stub } from "./mock.ts";
import { TestSuiteInternal } from "./_test_suite.ts";

class TestContext implements Deno.TestContext {
name: string;
origin: string;
steps: TestContext[];
spies: {
step: Spy;
};

constructor(name: string) {
this.name = name;
this.origin = "origin";
this.spies = {
step: spy(this, "step"),
};
this.steps = [];
}

async step(t: Deno.TestStepDefinition): Promise<boolean>;
async step(
name: string,
fn: (t: Deno.TestContext) => void | Promise<void>,
): Promise<boolean>;
async step(
fn: (t: Deno.TestContext) => void | Promise<void>,
): Promise<boolean>;
async step(
tOrNameOrFn:
| Deno.TestStepDefinition
| string
| ((t: Deno.TestContext) => void | Promise<void>),
fn?: (t: Deno.TestContext) => void | Promise<void>,
): Promise<boolean> {
let ignore = false;
if (typeof tOrNameOrFn === "function") {
ignore = false;
fn = tOrNameOrFn;
} else if (typeof tOrNameOrFn === "object") {
ignore = tOrNameOrFn.ignore ?? false;
fn = tOrNameOrFn.fn;
}

const name = typeof tOrNameOrFn === "string"
? tOrNameOrFn
: tOrNameOrFn.name;
const context = new TestContext(name);
this.steps.push(context);
if (!ignore) {
await fn!(context);
}
return !ignore;
}
}

async function assertDescribeOptions<T>(
expectedOptions: Omit<Deno.TestDefinition, "name" | "fn">,
cb: (fn: Spy) => void,
) {
using test = stub(Deno, "test");
const fn = spy();
try {
cb(fn);

assertSpyCalls(fn, 0);
assertSpyCall(test, 0);
const call = test.calls[0];
const options = call?.args[0] as Deno.TestDefinition;
assertEquals(
Object.keys(options).sort(),
["name", "fn", ...Object.keys(expectedOptions)].sort(),
);
assertObjectMatch(options, {
name: "example",
...expectedOptions,
});

const context = new TestContext("example");
const result = options.fn(context);
assertStrictEquals(Promise.resolve(result), result);
assertEquals(await result, undefined);
assertSpyCalls(context.spies.step, 0);
assertSpyCall(fn, 0, {
self: {},
args: [context],
returned: undefined,
});
} finally {
TestSuiteInternal.reset();
}
}

export async function assertMinimumDescribeOptions(
cb: (fn: Spy) => void,
) {
await assertDescribeOptions({}, cb);
}

async function assertItOptions(
expectedOptions: Omit<Deno.TestDefinition, "name" | "fn">,
cb: (fns: readonly [Spy, Spy]) => void,
) {
using test = stub(Deno, "test");
const fns = [spy(), spy()] as const;
try {
cb(fns);

assertSpyCall(test, 0);
const call = test.calls[0];
const options = call?.args[0] as Deno.TestDefinition;
assertEquals(
Object.keys(options).sort(),
["name", "fn", ...Object.keys(expectedOptions)].sort(),
);
assertObjectMatch(options, {
name: "example",
...expectedOptions,
});

assertSpyCalls(fns[0], 0);
assertSpyCalls(fns[1], 0);

const context = new TestContext("example");
const result = options.fn(context);
assertStrictEquals(Promise.resolve(result), result);
assertEquals(await result, undefined);
assertSpyCalls(context.spies.step, 2);

let fn = fns[0];
assertSpyCall(fn, 0, {
self: {},
args: [context.steps[0]],
returned: undefined,
});

fn = fns[1];
assertSpyCall(fn, 0, {
self: {},
args: [context.steps[1]],
returned: undefined,
});
assertSpyCalls(fn, 1);
} finally {
TestSuiteInternal.reset();
}
}

export async function assertMinimumItOptions(
cb: (fns: readonly [Spy, Spy]) => void,
) {
await assertItOptions({}, cb);
}
2 changes: 1 addition & 1 deletion testing/bdd_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1957,7 +1957,7 @@ Deno.test("describe()", async (t) => {
});

await t.step(
"mutiple hook calls",
"multiple hook calls",
async () => {
using test = stub(Deno, "test");
const context = new TestContext("example");
Expand Down
83 changes: 83 additions & 0 deletions testing/unstable_bdd.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import { globalSanitizersState } from "./_test_suite.ts";
import type { DescribeArgs, ItArgs, TestSuite } from "./bdd.ts";
import { describe as describe_, it as it_, test as test_ } from "./bdd.ts";

const describe = describe_ as typeof describe_ & describe;

Check failure on line 7 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Uncaught (in promise) Error: error[missing-jsdoc]: exported symbol is missing JSDoc documentation

Check failure on line 7 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

Uncaught (in promise) Error: error[missing-jsdoc]: exported symbol is missing JSDoc documentation
const it = it_ as typeof it_ & it;

Check failure on line 8 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

exported symbol is missing JSDoc documentation

Check failure on line 8 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

exported symbol is missing JSDoc documentation
const test = test_ as typeof test_ & test;

Check failure on line 9 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

exported symbol is missing JSDoc documentation

Check failure on line 9 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

exported symbol is missing JSDoc documentation

// deno-lint-ignore deno-style-guide/naming-convention
interface describe {

Check failure on line 12 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

exported symbol is missing JSDoc documentation

Check failure on line 12 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

exported symbol is missing JSDoc documentation
/**
* Register a test case that is not yet implemented. Alias of `.ignore()`.
*/
todo<T>(...args: DescribeArgs<T>): TestSuite<T>;
}

// deno-lint-ignore deno-style-guide/naming-convention
interface it {

Check failure on line 20 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

exported symbol is missing JSDoc documentation

Check failure on line 20 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

exported symbol is missing JSDoc documentation
todo<T>(...args: ItArgs<T>): void;

Check failure on line 21 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

exported symbol is missing JSDoc documentation

Check failure on line 21 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

exported symbol is missing JSDoc documentation
}

// deno-lint-ignore deno-style-guide/naming-convention
interface test {

Check failure on line 25 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

exported symbol is missing JSDoc documentation

Check failure on line 25 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

exported symbol is missing JSDoc documentation
todo<T>(...args: ItArgs<T>): void;

Check failure on line 26 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

exported symbol is missing JSDoc documentation

Check failure on line 26 in testing/unstable_bdd.ts

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

exported symbol is missing JSDoc documentation
}

/** Options for {@linkcode configureGlobalSanitizers}. */
export type ConfigureGlobalSanitizersOptions = {
Expand All @@ -25,3 +49,62 @@
globalSanitizersState.sanitizeResources = options.sanitizeResources;
globalSanitizersState.sanitizeExit = options.sanitizeExit;
}

/**
* Register a test suite that is not yet implemented.
*
* @example Usage
* ```ts
* import { describe, it, beforeAll } from "@std/testing/bdd";
* import { assertEquals } from "@std/assert";
*
* describe.todo("example");
* ```
*
* @param args The test suite body
*/
describe.todo = function describeTodo<T>(
...args: DescribeArgs<T>
): TestSuite<T> {
return describe.ignore(...args);
};

/**
* Register a test case that is not yet implemented.
*
* @example Usage
* ```ts
* import { describe, it } from "@std/testing/bdd";
* import { assertEquals } from "@std/assert";
*
* describe("example", () => {
* it.todo("should pass", () => {});
* });
* ```
*
* @param args The test case
*/
it.todo = function itTodo<T>(...args: ItArgs<T>): void {
it.ignore(...args);
};

/**
* Register a test case that is not yet implemented.
*
* @example Usage
* ```ts
* import { describe, test } from "@std/testing/bdd";
* import { assertEquals } from "@std/assert";
*
* describe("example", () => {
* test.todo("should pass", () => {});
* });
* ```
*
* @param args The test case
*/
test.todo = function itTodo<T>(...args: ItArgs<T>): void {
it.todo(...args);
};

export { describe, it, test };
30 changes: 29 additions & 1 deletion testing/unstable_bdd_test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import { assertEquals } from "@std/assert";
import { assert, assertEquals } from "@std/assert";
import { describe, it } from "./unstable_bdd.ts";
import {
assertMinimumDescribeOptions,
assertMinimumItOptions,
} from "./_test_helpers.ts";

Deno.test("configureGlobalSanitizers() modifies the test sanitizers globally", async () => {
{
Expand Down Expand Up @@ -36,3 +41,26 @@ Deno.test("configureGlobalSanitizers() modifies the test sanitizers globally", a
assertEquals(output.code, 42);
}
});

Deno.test("describe.todo()", async (t) => {
await t.step(
"minimum options (todo)",
async () =>
await assertMinimumDescribeOptions((fns) => {
const suite = describe.todo({ name: "example" });
assert(suite && typeof suite.symbol === "symbol");
it({ suite, name: "a", fn: fns[0] });
it({ suite, name: "b", fn: fns[1] });
}),
);
});

Deno.test("it.todo()", async (t) => {
await t.step(
"minimum options (todo)",
async () =>
await assertMinimumItOptions((fn) => {
it.todo({ name: "example", fn });
}),
);
});
Loading