From e6113236671b4ba2a4ce7da0af506fdf412f2e89 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sun, 15 Mar 2026 14:20:24 +0100 Subject: [PATCH 1/4] test_runner: add context subtests `expectFailure` `only` `skip` `todo` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Heath Dutton🕴️ Co-Authored-By: Felipe <60716370+felipeness@users.noreply.github.com> --- lib/internal/test_runner/test.js | 15 ++++-- test/parallel/test-runner-subtest-methods.js | 53 ++++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-runner-subtest-methods.js diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js index 2203bbd3497659..f3b9ba26bb610c 100644 --- a/lib/internal/test_runner/test.js +++ b/lib/internal/test_runner/test.js @@ -14,6 +14,7 @@ const { MathMax, Number, NumberPrototypeToFixed, + ObjectAssign, ObjectKeys, ObjectSeal, Promise, @@ -358,11 +359,11 @@ class TestContext { this.#test.todo(message); } - test(name, options, fn) { - const overrides = { + #runTest(name, options, fn, extraOverrides) { + const overrides = ObjectAssign({ __proto__: null, loc: getCallerLocation(), - }; + }, extraOverrides); const { plan } = this.#test; if (plan !== null) { @@ -377,6 +378,14 @@ class TestContext { return subtest.start(); } + test = ObjectAssign((...args) => this.#runTest(...args), { + __proto__: null, + expectFailure: (name, opts, fn) => this.#runTest(name, opts, fn, { __proto__: null, expectFailure: true }), + only: (name, opts, fn) => this.#runTest(name, opts, fn, { __proto__: null, only: true }), + skip: (name, opts, fn) => this.#runTest(name, opts, fn, { __proto__: null, skip: true }), + todo: (name, opts, fn) => this.#runTest(name, opts, fn, { __proto__: null, todo: true }), + }); + before(fn, options) { this.#test.createHook('before', fn, { __proto__: null, diff --git a/test/parallel/test-runner-subtest-methods.js b/test/parallel/test-runner-subtest-methods.js new file mode 100644 index 00000000000000..08ddef85558798 --- /dev/null +++ b/test/parallel/test-runner-subtest-methods.js @@ -0,0 +1,53 @@ +'use strict'; + +require('../common'); +const assert = require('node:assert'); +const { test } = require('node:test'); +const { isPromise } = require('node:util/types'); + +test('subtest context should have test variants', async (t) => { + assert.strictEqual(typeof t.test, 'function'); + assert.strictEqual(typeof t.test.expectFailure, 'function'); + assert.strictEqual(typeof t.test.only, 'function'); + assert.strictEqual(typeof t.test.skip, 'function'); + assert.strictEqual(typeof t.test.todo, 'function'); +}); + +test('subtest should return a promise', async (t) => { + const normal = t.test('normal subtest'); + assert.ok(isPromise(normal)); + await normal; +}); + +test('t.test[variant]() should return a promise', async (t) => { + assert.ok(isPromise( + t.test.expectFailure('expectFailure subtest', () => { throw new Error('This should pass'); }) + )); + assert.ok(isPromise( + t.test.only('only subtest') + )); + assert.ok(isPromise( + t.test.skip('skip subtest') + )); + assert.ok(isPromise( + t.test.todo('todo subtest') + )); +}); + +test('nested subtests should have test variants', async (t) => { + await t.test('level 1', async (t) => { + assert.strictEqual(typeof t.test, 'function'); + assert.strictEqual(typeof t.test.expectFailure, 'function'); + assert.strictEqual(typeof t.test.only, 'function'); + assert.strictEqual(typeof t.test.skip, 'function'); + assert.strictEqual(typeof t.test.todo, 'function'); + + await t.test('level 2', async (t) => { + assert.strictEqual(typeof t.test, 'function'); + assert.strictEqual(typeof t.test.expectFailure, 'function'); + assert.strictEqual(typeof t.test.skip, 'function'); + assert.strictEqual(typeof t.test.todo, 'function'); + assert.strictEqual(typeof t.test.only, 'function'); + }); + }); +}); From d5e777d6c7ff623db15d18a831ec52ee240f5fb2 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sun, 15 Mar 2026 15:06:47 +0100 Subject: [PATCH 2/4] fixup!: try `await`ing subtests --- test/parallel/test-runner-subtest-methods.js | 26 +++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/test/parallel/test-runner-subtest-methods.js b/test/parallel/test-runner-subtest-methods.js index 08ddef85558798..e43e6d9595268a 100644 --- a/test/parallel/test-runner-subtest-methods.js +++ b/test/parallel/test-runner-subtest-methods.js @@ -20,18 +20,20 @@ test('subtest should return a promise', async (t) => { }); test('t.test[variant]() should return a promise', async (t) => { - assert.ok(isPromise( - t.test.expectFailure('expectFailure subtest', () => { throw new Error('This should pass'); }) - )); - assert.ok(isPromise( - t.test.only('only subtest') - )); - assert.ok(isPromise( - t.test.skip('skip subtest') - )); - assert.ok(isPromise( - t.test.todo('todo subtest') - )); + const xfail = t.test.expectFailure('expectFailure subtest', () => { throw new Error('This should pass'); }); + const only = t.test.only('only subtest'); + const skip = t.test.skip('skip subtest'); + const todo = t.test.todo('todo subtest'); + + assert.ok(isPromise(xfail)); + assert.ok(isPromise(only)); + assert.ok(isPromise(skip)); + assert.ok(isPromise(todo)); + + await xfail; + await only; + await skip; + await todo; }); test('nested subtests should have test variants', async (t) => { From db192c3d8cd3edb8e4476e6cbfcaa9802634e091 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sun, 15 Mar 2026 15:44:33 +0100 Subject: [PATCH 3/4] Revert "fixup!: try `await`ing subtests" This reverts commit d5e777d6c7ff623db15d18a831ec52ee240f5fb2. --- test/parallel/test-runner-subtest-methods.js | 26 +++++++++----------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/test/parallel/test-runner-subtest-methods.js b/test/parallel/test-runner-subtest-methods.js index e43e6d9595268a..08ddef85558798 100644 --- a/test/parallel/test-runner-subtest-methods.js +++ b/test/parallel/test-runner-subtest-methods.js @@ -20,20 +20,18 @@ test('subtest should return a promise', async (t) => { }); test('t.test[variant]() should return a promise', async (t) => { - const xfail = t.test.expectFailure('expectFailure subtest', () => { throw new Error('This should pass'); }); - const only = t.test.only('only subtest'); - const skip = t.test.skip('skip subtest'); - const todo = t.test.todo('todo subtest'); - - assert.ok(isPromise(xfail)); - assert.ok(isPromise(only)); - assert.ok(isPromise(skip)); - assert.ok(isPromise(todo)); - - await xfail; - await only; - await skip; - await todo; + assert.ok(isPromise( + t.test.expectFailure('expectFailure subtest', () => { throw new Error('This should pass'); }) + )); + assert.ok(isPromise( + t.test.only('only subtest') + )); + assert.ok(isPromise( + t.test.skip('skip subtest') + )); + assert.ok(isPromise( + t.test.todo('todo subtest') + )); }); test('nested subtests should have test variants', async (t) => { From 6597499743be784b5f75db8c30de178518cf4d73 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sat, 28 Mar 2026 21:46:51 +0100 Subject: [PATCH 4/4] remove over-complication to `runTest` signature --- lib/internal/test_runner/test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js index f3b9ba26bb610c..174671e034f49f 100644 --- a/lib/internal/test_runner/test.js +++ b/lib/internal/test_runner/test.js @@ -359,11 +359,11 @@ class TestContext { this.#test.todo(message); } - #runTest(name, options, fn, extraOverrides) { - const overrides = ObjectAssign({ + #runTest(name, options, fn) { + const overrides = { __proto__: null, loc: getCallerLocation(), - }, extraOverrides); + }; const { plan } = this.#test; if (plan !== null) { @@ -380,10 +380,10 @@ class TestContext { test = ObjectAssign((...args) => this.#runTest(...args), { __proto__: null, - expectFailure: (name, opts, fn) => this.#runTest(name, opts, fn, { __proto__: null, expectFailure: true }), - only: (name, opts, fn) => this.#runTest(name, opts, fn, { __proto__: null, only: true }), - skip: (name, opts, fn) => this.#runTest(name, opts, fn, { __proto__: null, skip: true }), - todo: (name, opts, fn) => this.#runTest(name, opts, fn, { __proto__: null, todo: true }), + expectFailure: (name, opts, fn) => this.#runTest(name, { __proto__: null, ...opts, expectFailure: true }, fn), + only: (name, opts, fn) => this.#runTest(name, { __proto__: null, ...opts, only: true }, fn), + skip: (name, opts, fn) => this.#runTest(name, { __proto__: null, ...opts, skip: true }, fn), + todo: (name, opts, fn) => this.#runTest(name, { __proto__: null, ...opts, todo: true }, fn), }); before(fn, options) {