diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js index 866d7d1791e298..1253a815118ca0 100644 --- a/lib/internal/test_runner/test.js +++ b/lib/internal/test_runner/test.js @@ -1,5 +1,6 @@ 'use strict'; const { + ArrayPrototypeMap, ArrayPrototypePush, ArrayPrototypeReduce, ArrayPrototypeShift, @@ -18,6 +19,9 @@ const { SafeSet, SafePromiseAll, SafePromiseRace, + StringPrototypeReplace, + StringPrototypeSplit, + StringPrototypeTrim, Symbol, } = primordials; const { AsyncResource } = require('async_hooks'); @@ -225,11 +229,28 @@ class Test extends AsyncResource { } if (testNamePatterns !== null) { - // eslint-disable-next-line no-use-before-define - const match = this instanceof TestHook || ArrayPrototypeSome( - testNamePatterns, - (re) => RegExpPrototypeExec(re, name) !== null, - ); + const match = + // eslint-disable-next-line no-use-before-define + this instanceof TestHook || + ArrayPrototypeSome(testNamePatterns, (re) => { + const scopedTestNamePatterns = StringPrototypeSplit(re, '>'); + if (name !== '' && scopedTestNamePatterns.length > 1) { + const patterns = ArrayPrototypeMap( + scopedTestNamePatterns, + (pattern) => { + const text = StringPrototypeReplace(pattern, '/', ''); + return StringPrototypeTrim(text); + } + ); + + for (let p = 0; p < patterns.length; p++) { + if (name === patterns[p]) { + return p === 0 || parent?.name === patterns[p - 1]; + } + } + } + return RegExpPrototypeExec(re, name) !== null; + }); if (!match) { skip = 'test name does not match pattern'; diff --git a/test/message/test_runner_test_name_pattern_filtering.js b/test/message/test_runner_test_name_pattern_filtering.js new file mode 100644 index 00000000000000..e494a9cbb500dc --- /dev/null +++ b/test/message/test_runner_test_name_pattern_filtering.js @@ -0,0 +1,63 @@ +// Flags: --no-warnings --test-name-pattern=suite-one>suite-two>subtest-one +'use strict'; +const common = require('../common'); +const { + after, + afterEach, + before, + beforeEach, + describe, + it, + test, +} = require('node:test'); + +describe('suite-one', () => { + before(common.mustCall()); + beforeEach(common.mustCall(3)); + afterEach(common.mustCall(3)); + after(common.mustCall()); + + it('subtest-one', common.mustNotCall()); + describe( + 'suite-two', + common.mustCall(() => { + it('subtest-one', common.mustCall()); + }), + ); + describe('suite-three', common.mustNotCall()); +}); + +test( + 'suite-one', + common.mustCall(async (t) => { + t.beforeEach(common.mustCall(3)); + t.afterEach(common.mustCall(3)); + + await t.test('subtest-one', common.mustNotCall()); + await t.test( + 'suite-two', + common.mustCall(async (t) => { + await t.test('subtest-one', common.mustCall()); + }), + ); + await t.test('suite-three', common.mustNotCall()); + }), +); + +describe('excluded', () => { + before(common.mustNotCall()); + beforeEach(common.mustNotCall()); + afterEach(common.mustNotCall()); + after(common.mustNotCall()); + + it('subtest-one', common.mustNotCall()); + describe('suite-two', common.mustNotCall()); +}); + +test('excluded', (t) => { + t.beforeEach(common.mustNotCall()); + t.afterEach(common.mustNotCall()); + + t.test('subtest-one', common.mustNotCall()); + t.test('suite-two', common.mustNotCall()); +}); diff --git a/test/message/test_runner_test_name_pattern_filtering.out b/test/message/test_runner_test_name_pattern_filtering.out new file mode 100644 index 00000000000000..de07f1b503b3ce --- /dev/null +++ b/test/message/test_runner_test_name_pattern_filtering.out @@ -0,0 +1,73 @@ +TAP version 13 +# Subtest: suite-one + # Subtest: subtest-one + ok 1 - subtest-one # SKIP test name does not match pattern + --- + duration_ms: * + ... + # Subtest: suite-two + # Subtest: subtest-one + ok 1 - subtest-one + --- + duration_ms: * + ... + 1..1 + ok 2 - suite-two + --- + duration_ms: * + ... + # Subtest: suite-three + ok 3 - suite-three # SKIP test name does not match pattern + --- + duration_ms: * + ... + 1..3 +ok 1 - suite-one + --- + duration_ms: * + ... +# Subtest: suite-one + # Subtest: subtest-one + ok 1 - subtest-one # SKIP test name does not match pattern + --- + duration_ms: * + ... + # Subtest: suite-two + # Subtest: subtest-one + ok 1 - subtest-one + --- + duration_ms: * + ... + 1..1 + ok 2 - suite-two + --- + duration_ms: * + ... + # Subtest: suite-three + ok 3 - suite-three # SKIP test name does not match pattern + --- + duration_ms: * + ... + 1..3 +ok 2 - suite-one + --- + duration_ms: * + ... +# Subtest: excluded +ok 3 - excluded # SKIP test name does not match pattern + --- + duration_ms: * + ... +# Subtest: excluded +ok 4 - excluded # SKIP test name does not match pattern + --- + duration_ms: * + ... +1..4 +# tests 4 +# pass 2 +# fail 0 +# cancelled 0 +# skipped 2 +# todo 0 +# duration_ms *