From e5d46e86240239615d4c6a7060a33973fad0928f Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Sat, 12 Apr 2025 09:45:57 -0700 Subject: [PATCH] Expose spec path as an array of names This is in addition to the existing concatenated name. It's meant to support tools like IDE integrations that want to be able to filter a run to an exact set of suites/specs. --- lib/jasmine-core/jasmine.js | 52 +++++++++++++++++++++-------------- spec/core/SpecSpec.js | 34 +++++++++++++++++------ spec/core/SuiteBuilderSpec.js | 14 ++++++++++ src/core/Spec.js | 27 +++++++++++++----- src/core/SuiteBuilder.js | 25 ++++++++--------- 5 files changed, 103 insertions(+), 49 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index c6665c2f..76dbb7bb 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -792,11 +792,11 @@ getJasmineRequireObj().Spec = function(j$) { this.onStart = attrs.onStart || function() {}; this.autoCleanClosures = attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures; - this.getSpecName = - attrs.getSpecName || - function() { - return ''; - }; + + this.getPath = function() { + return attrs.getPath ? attrs.getPath(this) : []; + }; + this.onLateError = attrs.onLateError || function() {}; this.catchingExceptions = attrs.catchingExceptions || @@ -1022,7 +1022,7 @@ getJasmineRequireObj().Spec = function(j$) { }; Spec.prototype.getFullName = function() { - return this.getSpecName(this); + return this.getPath().join(' '); }; Spec.prototype.addDeprecationWarning = function(deprecation) { @@ -1076,6 +1076,10 @@ getJasmineRequireObj().Spec = function(j$) { * @since 2.0.0 */ Object.defineProperty(Spec.prototype, 'metadata', { + // NOTE: Although most of jasmine-core only exposes these metadata objects, + // actual Spec instances are still passed to Configuration#specFilter. Until + // that is fixed, it's important to make sure that all metadata properties + // also exist in compatible form on the underlying Spec. get: function() { if (!this.metadata_) { this.metadata_ = { @@ -1104,7 +1108,16 @@ getJasmineRequireObj().Spec = function(j$) { * @returns {string} * @since 2.0.0 */ - getFullName: this.getFullName.bind(this) + getFullName: this.getFullName.bind(this), + + /** + * The full path of the spec, as an array of names. + * @name Spec#getPath + * @function + * @returns {Array.} + * @since 5.7.0 + */ + getPath: this.getPath.bind(this) }; } @@ -10947,9 +10960,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { resultCallback: (result, next) => { this.specResultCallback_(spec, result, next); }, - getSpecName: function(spec) { - return getSpecName(spec, suite); - }, + getPath: spec => this.getSpecPath_(spec, suite), onStart: (spec, next) => this.specStarted_(spec, suite, next), description: description, userContext: function() { @@ -10966,6 +10977,17 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { return spec; } + getSpecPath_(spec, suite) { + const path = [spec.description]; + + while (suite && suite !== this.topSuite) { + path.unshift(suite.description); + suite = suite.parentSuite; + } + + return path; + } + unfocusAncestor_() { const focusedAncestor = findFocusedAncestor( this.currentDeclarationSuite_ @@ -11029,16 +11051,6 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { }; } - function getSpecName(spec, suite) { - const fullName = [spec.description], - suiteFullName = suite.getFullName(); - - if (suiteFullName !== '') { - fullName.unshift(suiteFullName); - } - return fullName.join(' '); - } - return SuiteBuilder; }; diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index 599760ed..bcde9478 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -197,8 +197,8 @@ describe('Spec', function() { description: 'with a spec', parentSuiteId: 'suite1', filename: 'someSpecFile.js', - getSpecName: function() { - return 'a suite with a spec'; + getPath: function() { + return ['a suite', 'with a spec']; }, queueableFn: { fn: null } }); @@ -485,17 +485,33 @@ describe('Spec', function() { }); it('can return its full name', function() { - const specNameSpy = jasmine - .createSpy('specNameSpy') - .and.returnValue('expected val'); + const getPath = jasmine + .createSpy('getPath') + .and.returnValue(['expected', 'val']); const spec = new jasmineUnderTest.Spec({ - getSpecName: specNameSpy, + getPath, queueableFn: { fn: null } }); expect(spec.getFullName()).toBe('expected val'); - expect(specNameSpy.calls.mostRecent().args[0].id).toEqual(spec.id); + expect(getPath.calls.mostRecent().args[0]).toBe(spec); + }); + + it('can return its full path', function() { + const getPath = jasmine + .createSpy('getPath') + .and.returnValue(['expected val']); + + const spec = new jasmineUnderTest.Spec({ + getPath, + queueableFn: { fn: null } + }); + + expect(spec.getPath()).toEqual(['expected val']); + expect(getPath.calls.mostRecent().args[0]).toBe(spec); + + expect(spec.metadata.getPath()).toEqual(['expected val']); }); describe('when a spec is marked pending during execution', function() { @@ -586,8 +602,8 @@ describe('Spec', function() { spec = new jasmineUnderTest.Spec({ onLateError: onLateError, queueableFn: { fn: function() {} }, - getSpecName: function() { - return 'a spec'; + getPath: function() { + return ['a spec']; } }); diff --git a/spec/core/SuiteBuilderSpec.js b/spec/core/SuiteBuilderSpec.js index 0bde3a43..0f03579c 100644 --- a/spec/core/SuiteBuilderSpec.js +++ b/spec/core/SuiteBuilderSpec.js @@ -163,6 +163,20 @@ describe('SuiteBuilder', function() { expect(spec2.id).toMatch(/^spec[0-9]+$/); expect(spec1.id).not.toEqual(spec2.id); }); + + it('gives each spec a full path', function() { + const env = { configuration: () => ({}) }; + const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env }); + let spec; + + suiteBuilder.describe('a suite', function() { + suiteBuilder.describe('a nested suite', function() { + spec = suiteBuilder[fnName]('a spec', function() {}); + }); + }); + + expect(spec.getPath()).toEqual(['a suite', 'a nested suite', 'a spec']); + }); } function sameInstanceAs(expected) { diff --git a/src/core/Spec.js b/src/core/Spec.js index d3781159..6ba9870a 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -21,11 +21,11 @@ getJasmineRequireObj().Spec = function(j$) { this.onStart = attrs.onStart || function() {}; this.autoCleanClosures = attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures; - this.getSpecName = - attrs.getSpecName || - function() { - return ''; - }; + + this.getPath = function() { + return attrs.getPath ? attrs.getPath(this) : []; + }; + this.onLateError = attrs.onLateError || function() {}; this.catchingExceptions = attrs.catchingExceptions || @@ -251,7 +251,7 @@ getJasmineRequireObj().Spec = function(j$) { }; Spec.prototype.getFullName = function() { - return this.getSpecName(this); + return this.getPath().join(' '); }; Spec.prototype.addDeprecationWarning = function(deprecation) { @@ -305,6 +305,10 @@ getJasmineRequireObj().Spec = function(j$) { * @since 2.0.0 */ Object.defineProperty(Spec.prototype, 'metadata', { + // NOTE: Although most of jasmine-core only exposes these metadata objects, + // actual Spec instances are still passed to Configuration#specFilter. Until + // that is fixed, it's important to make sure that all metadata properties + // also exist in compatible form on the underlying Spec. get: function() { if (!this.metadata_) { this.metadata_ = { @@ -333,7 +337,16 @@ getJasmineRequireObj().Spec = function(j$) { * @returns {string} * @since 2.0.0 */ - getFullName: this.getFullName.bind(this) + getFullName: this.getFullName.bind(this), + + /** + * The full path of the spec, as an array of names. + * @name Spec#getPath + * @function + * @returns {Array.} + * @since 5.7.0 + */ + getPath: this.getPath.bind(this) }; } diff --git a/src/core/SuiteBuilder.js b/src/core/SuiteBuilder.js index ef6010c9..b1052d46 100644 --- a/src/core/SuiteBuilder.js +++ b/src/core/SuiteBuilder.js @@ -250,9 +250,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { resultCallback: (result, next) => { this.specResultCallback_(spec, result, next); }, - getSpecName: function(spec) { - return getSpecName(spec, suite); - }, + getPath: spec => this.getSpecPath_(spec, suite), onStart: (spec, next) => this.specStarted_(spec, suite, next), description: description, userContext: function() { @@ -269,6 +267,17 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { return spec; } + getSpecPath_(spec, suite) { + const path = [spec.description]; + + while (suite && suite !== this.topSuite) { + path.unshift(suite.description); + suite = suite.parentSuite; + } + + return path; + } + unfocusAncestor_() { const focusedAncestor = findFocusedAncestor( this.currentDeclarationSuite_ @@ -332,15 +341,5 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { }; } - function getSpecName(spec, suite) { - const fullName = [spec.description], - suiteFullName = suite.getFullName(); - - if (suiteFullName !== '') { - fullName.unshift(suiteFullName); - } - return fullName.join(' '); - } - return SuiteBuilder; };