From 85322d187755176ef0c4fe37c3da09a48b69f5ed Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Fri, 24 Oct 2025 17:26:49 -0700 Subject: [PATCH] Re-add support for partial spec name filtering No UI for this but users can construct URLs manually. Fixes #2085. --- lib/jasmine-core/boot1.js | 6 +++ lib/jasmine-core/jasmine-html.js | 44 +++++++++-------- lib/jasmine-core/jasmine.js | 2 + spec/html/HtmlSpecFilterV2Spec.js | 79 +++++++++++++++++++------------ src/boot/boot1.js | 6 +++ src/core/reporterEvents.js | 2 + src/html/HtmlReporterV2Urls.js | 12 +++-- src/html/HtmlSpecFilterv2.js | 32 ++++++------- 8 files changed, 114 insertions(+), 69 deletions(-) diff --git a/lib/jasmine-core/boot1.js b/lib/jasmine-core/boot1.js index d9ea7105..86d9dbd9 100644 --- a/lib/jasmine-core/boot1.js +++ b/lib/jasmine-core/boot1.js @@ -58,6 +58,12 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ env.addReporter(jsApiReporter); env.addReporter(htmlReporter); + /** + * Configures Jasmine based on the current set of query parameters. This + * supports all parameters set by the HTML reporter as well as + * spec=partialPath, which filters out specs whose paths don't contain the + * parameter. + */ env.configure(urls.configFromCurrentUrl()); /** diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index eb70d6e7..c1637e7b 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -1233,7 +1233,10 @@ jasmineRequire.HtmlReporterV2Urls = function(j$) { } /** - * Creates a {@link Configuration} from the current page's URL. + * Creates a {@link Configuration} from the current page's URL. Supported + * query string parameters include all those set by {@link HtmlReporterV2} + * as well as spec=partialPath, which filters out specs whose paths don't + * contain partialPath. * @returns {Configuration} * @example * const urls = new jasmine.HtmlReporterV2Urls(); @@ -1259,9 +1262,10 @@ jasmineRequire.HtmlReporterV2Urls = function(j$) { } const specFilter = new j$.private.HtmlSpecFilterV2({ - filterString: () => { - return this.queryString.getParam('path'); - } + filterParams: () => ({ + path: this.queryString.getParam('path'), + spec: this.queryString.getParam('spec') + }) }); config.specFilter = function(spec) { @@ -1281,35 +1285,35 @@ jasmineRequire.HtmlReporterV2Urls = function(j$) { jasmineRequire.HtmlSpecFilterV2 = function() { class HtmlSpecFilterV2 { - #getFilterString; + #getFilterParams; constructor(options) { - this.#getFilterString = options.filterString; + this.#getFilterParams = options.filterParams; } - /** - * Determines whether the spec with the specified name should be executed. - * @name HtmlSpecFilterV2#matches - * @function - * @param {Spec} spec - * @returns {boolean} - */ matches(spec) { - const filterString = this.#getFilterString(); + const params = this.#getFilterParams(); - if (!filterString) { - return true; + if (params.path) { + return this.#matchesPath(spec, JSON.parse(params.path)); + } else if (params.spec) { + // Like legacy HtmlSpecFilter, retained because it's convenient for + // hand-constructing filter URLs + return spec.getFullName().includes(params.spec); } - const filterPath = JSON.parse(this.#getFilterString()); + return true; + } + + #matchesPath(spec, path) { const specPath = spec.getPath(); - if (filterPath.length > specPath.length) { + if (path.length > specPath.length) { return false; } - for (let i = 0; i < filterPath.length; i++) { - if (specPath[i] !== filterPath[i]) { + for (let i = 0; i < path.length; i++) { + if (specPath[i] !== path[i]) { return false; } } diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 113e8619..6aa4b083 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -8905,6 +8905,7 @@ getJasmineRequireObj().reporterEvents = function(j$) { * {@link ReporterCapabilities} will apply. * @name Reporter#reporterCapabilities * @type ReporterCapabilities | undefined + * @optional * @since 5.0 */ /** @@ -8971,6 +8972,7 @@ getJasmineRequireObj().reporterEvents = function(j$) { /** * `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions) * @function + * @optional * @name Reporter#specStarted * @param {SpecStartedEvent} result Information about the individual {@link it} being run * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. diff --git a/spec/html/HtmlSpecFilterV2Spec.js b/spec/html/HtmlSpecFilterV2Spec.js index 16c175d7..b62db9fc 100644 --- a/spec/html/HtmlSpecFilterV2Spec.js +++ b/spec/html/HtmlSpecFilterV2Spec.js @@ -1,48 +1,69 @@ describe('HtmlSpecFilterV2', function() { - it('matches everything when no string is provided', function() { - const specFilter = new privateUnderTest.HtmlSpecFilterV2({ - filterString() { - return ''; - } - }); + describe('When both query parameters are falsy', function() { + it('matches everything', function() { + const specFilter = new privateUnderTest.HtmlSpecFilterV2({ + filterParams() { + return { path: '', spec: '' }; + } + }); - expect(specFilter.matches({})).toBeTrue(); + expect(specFilter.matches({})).toBeTrue(); + }); }); - it('matches a spec with the exact same path', function() { - const specFilter = new privateUnderTest.HtmlSpecFilterV2({ - filterString() { - return '["a","b","c"]'; - } + describe('When the path parameter is truthy', function() { + it('matches a spec with the exact same path', function() { + const specFilter = new privateUnderTest.HtmlSpecFilterV2({ + filterParams() { + return { path: '["a","b","c"]', spec: '' }; + } + }); + + expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue(); }); - expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue(); - }); + it('matches a spec whose path has the filter path as a prefix', function() { + const specFilter = new privateUnderTest.HtmlSpecFilterV2({ + filterParams() { + return { path: '["a","b"]', spec: '' }; + } + }); - it('matches a spec whose path has the filter path as a prefix', function() { - const specFilter = new privateUnderTest.HtmlSpecFilterV2({ - filterString() { - return '["a","b"]'; - } + expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue(); }); - expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue(); - }); + it('does not match a spec with a different path', function() { + const specFilter = new privateUnderTest.HtmlSpecFilterV2({ + filterParams() { + return { path: '["a","b","c"]', spec: '' }; + } + }); - it('does not match a spec with a different path', function() { - const specFilter = new privateUnderTest.HtmlSpecFilterV2({ - filterString() { - return '["a","b","c"]'; - } + expect(specFilter.matches(stubSpec(['a', 'd', 'c']))).toBeFalse(); }); - - expect(specFilter.matches(stubSpec(['a', 'd', 'c']))).toBeFalse(); }); - function stubSpec(path) { + describe('When the path parameter is falsy and the spec parameter is truthy', function() { + it('matches specs with full names containing the parameter value', function() { + const specFilter = new privateUnderTest.HtmlSpecFilterV2({ + filterParams() { + return { path: '', spec: 'bar' }; + } + }); + + expect(specFilter.matches(stubSpec('', 'foo bar baz'))).toBeTrue(); + expect(specFilter.matches(stubSpec('', 'foo baz'))).toBeFalse(); + expect(specFilter.matches(stubSpec('', 'sandbars'))).toBeTrue(); + }); + }); + + function stubSpec(path, fullName) { return { getPath() { return path; + }, + getFullName() { + return fullName; } }; } diff --git a/src/boot/boot1.js b/src/boot/boot1.js index 8de247e9..9502b917 100644 --- a/src/boot/boot1.js +++ b/src/boot/boot1.js @@ -34,6 +34,12 @@ */ env.addReporter(jsApiReporter); env.addReporter(htmlReporter); + /** + * Configures Jasmine based on the current set of query parameters. This + * supports all parameters set by the HTML reporter as well as + * spec=partialPath, which filters out specs whose paths don't contain the + * parameter. + */ env.configure(urls.configFromCurrentUrl()); /** diff --git a/src/core/reporterEvents.js b/src/core/reporterEvents.js index 633b1c4f..55243039 100644 --- a/src/core/reporterEvents.js +++ b/src/core/reporterEvents.js @@ -7,6 +7,7 @@ getJasmineRequireObj().reporterEvents = function(j$) { * {@link ReporterCapabilities} will apply. * @name Reporter#reporterCapabilities * @type ReporterCapabilities | undefined + * @optional * @since 5.0 */ /** @@ -73,6 +74,7 @@ getJasmineRequireObj().reporterEvents = function(j$) { /** * `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions) * @function + * @optional * @name Reporter#specStarted * @param {SpecStartedEvent} result Information about the individual {@link it} being run * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. diff --git a/src/html/HtmlReporterV2Urls.js b/src/html/HtmlReporterV2Urls.js index bb48b47f..cfa66db8 100644 --- a/src/html/HtmlReporterV2Urls.js +++ b/src/html/HtmlReporterV2Urls.js @@ -21,7 +21,10 @@ jasmineRequire.HtmlReporterV2Urls = function(j$) { } /** - * Creates a {@link Configuration} from the current page's URL. + * Creates a {@link Configuration} from the current page's URL. Supported + * query string parameters include all those set by {@link HtmlReporterV2} + * as well as spec=partialPath, which filters out specs whose paths don't + * contain partialPath. * @returns {Configuration} * @example * const urls = new jasmine.HtmlReporterV2Urls(); @@ -47,9 +50,10 @@ jasmineRequire.HtmlReporterV2Urls = function(j$) { } const specFilter = new j$.private.HtmlSpecFilterV2({ - filterString: () => { - return this.queryString.getParam('path'); - } + filterParams: () => ({ + path: this.queryString.getParam('path'), + spec: this.queryString.getParam('spec') + }) }); config.specFilter = function(spec) { diff --git a/src/html/HtmlSpecFilterv2.js b/src/html/HtmlSpecFilterv2.js index a875b132..5e93b3cc 100644 --- a/src/html/HtmlSpecFilterv2.js +++ b/src/html/HtmlSpecFilterv2.js @@ -1,34 +1,34 @@ jasmineRequire.HtmlSpecFilterV2 = function() { class HtmlSpecFilterV2 { - #getFilterString; + #getFilterParams; constructor(options) { - this.#getFilterString = options.filterString; + this.#getFilterParams = options.filterParams; } - /** - * Determines whether the spec with the specified name should be executed. - * @name HtmlSpecFilterV2#matches - * @function - * @param {Spec} spec - * @returns {boolean} - */ matches(spec) { - const filterString = this.#getFilterString(); + const params = this.#getFilterParams(); - if (!filterString) { - return true; + if (params.path) { + return this.#matchesPath(spec, JSON.parse(params.path)); + } else if (params.spec) { + // Like legacy HtmlSpecFilter, retained because it's convenient for + // hand-constructing filter URLs + return spec.getFullName().includes(params.spec); } - const filterPath = JSON.parse(this.#getFilterString()); + return true; + } + + #matchesPath(spec, path) { const specPath = spec.getPath(); - if (filterPath.length > specPath.length) { + if (path.length > specPath.length) { return false; } - for (let i = 0; i < filterPath.length; i++) { - if (specPath[i] !== filterPath[i]) { + for (let i = 0; i < path.length; i++) { + if (specPath[i] !== path[i]) { return false; } }