From 9a67c4e24d1479968abbbcd1f1a12b5ead0dcc2c Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Sat, 18 Oct 2025 13:05:29 -0700 Subject: [PATCH 1/7] Copy 6.0.0-alpha.1 release notes from branch --- release_notes/6.0.0-alpha.1.md | 116 +++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 release_notes/6.0.0-alpha.1.md diff --git a/release_notes/6.0.0-alpha.1.md b/release_notes/6.0.0-alpha.1.md new file mode 100644 index 00000000..c1ceab40 --- /dev/null +++ b/release_notes/6.0.0-alpha.1.md @@ -0,0 +1,116 @@ +# Jasmine Core 6.0.0-alpha.1 Release Notes + +This is a pre-release, intended to offer a preview of breaking changes and to +solicit feedback. + +## A Note About Pre-Release Compatibility + +There may be additional breaking changes in future 6.0 pre-releases or in the +final 6.0 release. That's allowed by the semver specification, but users are +sometimes unpleasantly surprised by it. + +NPM's implementation of carat version ranges assumes that subsequent +pre-releases and final releases are fully compatible with earlier pre-releases. +If your package.json contains `"jasmine-core": "^6.0.0-alpha.1`, +NPM might install any later 6.x version even though there is no guarantee of +compatibility. If that isn't ok, you should specify an exact pre-release version: +`"jasmine-core": "6.0.0-alpha.1`. + + +## Breaking changes + +### Changes that affect reporters + +* Irrelevant properties such as `status` and `failedExpectations` are omitted + from [the event passed to suiteStarted](https://jasmine.github.io/api/6.0.0-alpha.1/global.html#SuiteStartedEvent). + + This change should be compatible with most existing reporters but could break + reporters that manage their internal state in unusual ways. Please + [open an issue](https://github.com/jasmine/jasmine/issues/new?template=bug_report.yml) + if you find a published reporter package that works with jasmine-core 5.x but + not with this release. + +### Changes that affect browser boot files + +* The `createElement` and `createTextNode` options of `HtmlReporter` are ignored. + `HtmlReporter` now unconditionally uses `document.createElement` and + `document.createTextNode`. + +### Changes that affect spec writing + +* HTML reporters cache configuration throughout each run. Configuration changes + made while specs are running will not affect reporter behavior. +* Global error spies always receive a single argument. Previously, the browser + error event was passed as the second argument. + +## New features + +* A new `HtmlReporterV2` with several improvements over the old `HtmlReporter`: + * Clicking a spec/suite link does exact filtering rather than a substring + match. + * The old dots are replaced with a progress bar. This improves usability with + large suites and fixes an accessibility problem. + * Details of failed specs are displayed as soon as each spec finishes. + * Initialization and wire-up in boot files are much simpler. + + If you're using jasmine-browser-runner or copying boot1.js from the standalone + distribution, you'll automatically get the new reporter. If you maintain your + own boot files, you'll get the old reporter unless you update your boot1.js + to match the one that's in this package. + + The new reporter produces `spec` query string parameters that are different + from those created by the old reporter. If you use non-Jasmine software that + interprets the `spec` parameter, such as karma-jasmine, you may not be able to + adopt `HtmlReporterV2` unlesss it's updated. +* Use `globalThis` to determine the global object during initialization + This makes jasmine-core more tolerant of buggy bundlers or loaders that + cause `this` to be undefined in the global context. + + +## Deprecations + +* Warn if jasmine-core is loaded as an ES module in a browser. + This is an untested and unsupported configuration that has been known to cause + problems in the past. +* Deprecated `HtmlReporter` and `HtmlSpecFilter` in favor of `HtmlReporterV2`. + + +## Documentation improvements + +* Improved API reference documentation for APIs that are used from browser boot + files. + + +## Internal improvements + +* Removed remaining code that supported suite re-entry. +* Encapsulated suite and spec result and status management. +* Adopted strict mode throughout the codebase. +* Decomposed `HtmlReporter` into components and converted to ES6 classes. +* Made global error handling more uniform between browsers and Node. + + +## Supported environments + +This version has been tested in the following environments. + +| Environment | Supported versions | +|-------------------|--------------------------------| +| Node | 20, 22, 24 | +| Safari | 16**, 17** | +| Chrome | 141* | +| Firefox | 102**, 115**, 128**, 140, 143* | +| Edge | 141* | + +\* Evergreen browser. Each version of Jasmine is tested against the latest +version available at release time.
+\** Supported on a best-effort basis. Support for these versions may be dropped +if it becomes impractical, and bugs affecting only these versions may not be +treated as release blockers. + + + + +------ + +_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_ From 75658e05662d4033ae1d9a9d55f31e2b9b4cffc7 Mon Sep 17 00:00:00 2001 From: Jonah Bron Date: Thu, 23 Oct 2025 12:52:52 -0700 Subject: [PATCH 2/7] jasmine.allOf AsymmetricEqualityTester New asymmetric equality tester that accepts a variable number of arguments, and will pass if all of them evaluate as being equal to the input value. Includes unit tests --- spec/core/asymmetric_equality/AllOfSpec.js | 63 ++++++++++++++++++++++ src/core/asymmetric_equality/AllOf.js | 27 ++++++++++ src/core/base.js | 13 +++++ src/core/requireCore.js | 1 + 4 files changed, 104 insertions(+) create mode 100644 spec/core/asymmetric_equality/AllOfSpec.js create mode 100644 src/core/asymmetric_equality/AllOf.js diff --git a/spec/core/asymmetric_equality/AllOfSpec.js b/spec/core/asymmetric_equality/AllOfSpec.js new file mode 100644 index 00000000..6442ede0 --- /dev/null +++ b/spec/core/asymmetric_equality/AllOfSpec.js @@ -0,0 +1,63 @@ +describe('AllOf', function() { + it('matches a single value', function() { + const matchersUtil = new jasmineUnderTest.MatchersUtil(); + const allOf = new jasmineUnderTest.AllOf('foo'); + + expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeTrue(); + }); + + it('matches a single matcher', function() { + const matchersUtil = new jasmineUnderTest.MatchersUtil(); + const allOf = new jasmineUnderTest.AllOf( + new jasmineUnderTest.StringContaining('oo') + ); + + expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeTrue(); + }); + + it('matches multiple matchers', function() { + const matchersUtil = new jasmineUnderTest.MatchersUtil(); + const allOf = new jasmineUnderTest.AllOf( + new jasmineUnderTest.StringContaining('o'), + new jasmineUnderTest.StringContaining('f') + ); + + expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeTrue(); + }); + + it('does not match when value does not match', function() { + const matchersUtil = new jasmineUnderTest.MatchersUtil(); + const allOf = new jasmineUnderTest.AllOf('bar'); + + expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeFalse(); + }); + + it('does not match when any matchers fail', function() { + const matchersUtil = new jasmineUnderTest.MatchersUtil(); + const allOf = new jasmineUnderTest.AllOf( + new jasmineUnderTest.StringContaining('o'), + new jasmineUnderTest.StringContaining('x') + ); + + expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeFalse(); + }); + + it('jasmineToStrings itself', function() { + const matcher = new jasmineUnderTest.AllOf('o'); + const pp = jasmine.createSpy('pp').and.returnValue('sample'); + + expect(matcher.jasmineToString(pp)).toEqual(''); + expect(pp).toHaveBeenCalledWith(['o']); + }); + + describe('when called without an argument', function() { + it('tells the user to pass a constructor argument', function() { + expect(function() { + new jasmineUnderTest.AllOf(); + }).toThrowError( + TypeError, + 'jasmine.allOf() expects at least one argument to be passed.' + ); + }); + }); +}); diff --git a/src/core/asymmetric_equality/AllOf.js b/src/core/asymmetric_equality/AllOf.js new file mode 100644 index 00000000..db67a947 --- /dev/null +++ b/src/core/asymmetric_equality/AllOf.js @@ -0,0 +1,27 @@ +getJasmineRequireObj().AllOf = function(j$) { + function AllOf() { + const expectedValues = Array.from(arguments); + if (expectedValues.length === 0) { + throw new TypeError( + 'jasmine.allOf() expects at least one argument to be passed.' + ); + } + this.expectedValues = expectedValues; + } + + AllOf.prototype.asymmetricMatch = function(other, matchersUtil) { + for (const expectedValue of this.expectedValues) { + if (!matchersUtil.equals(other, expectedValue)) { + return false; + } + } + + return true; + }; + + AllOf.prototype.jasmineToString = function(pp) { + return ''; + }; + + return AllOf; +}; diff --git a/src/core/base.js b/src/core/base.js index 95ea4bba..608e83d7 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -221,6 +221,19 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { ); }; + /** + * Get an {@link AsymmetricEqualityTester} that will succeed if the actual + * value being compared matches every provided equality tester. + * @name asymmetricEqualityTesters.allOf + * @emittedName jasmine.allOf + * @since 5.13.0 + * @function + * @param {...*} arguments - The asymmetric equality checkers to compare. + */ + j$.allOf = function() { + return new j$.AllOf(...arguments); + }; + /** * Get an {@link AsymmetricEqualityTester} that will succeed if the actual * value being compared is an instance of the specified class/constructor. diff --git a/src/core/requireCore.js b/src/core/requireCore.js index 669bd244..49f7d729 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -35,6 +35,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) { j$.util = jRequire.util(j$); j$.errors = jRequire.errors(); j$.formatErrorMsg = jRequire.formatErrorMsg(); + j$.AllOf = jRequire.AllOf(j$); j$.Any = jRequire.Any(j$); j$.Anything = jRequire.Anything(j$); j$.CallTracker = jRequire.CallTracker(j$); From 27a1257b6d29f1a981b49ac81feef5fffc6cd27f Mon Sep 17 00:00:00 2001 From: bonkevin Date: Wed, 29 Oct 2025 13:04:10 -0400 Subject: [PATCH 3/7] fix: unavailable custom matchers on top-`it` --- spec/core/integration/EnvSpec.js | 28 ++++++++++++++++++++++++++++ src/core/TreeRunner.js | 5 ++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index d9224d40..b6bdbadf 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -2277,6 +2277,34 @@ describe('Env integration', function() { await env.execute(); }); + it('Custom matchers set in top-level beforeAll should be available to all specs and suites', async function() { + const matchers = { + toFoo: function() {} + }; + + env.beforeAll(function() { + env.addMatchers(matchers); + }); + + env.describe('suite - top-level', function() { + env.it('has access to the custom matcher', function() { + expect(env.expect().toFoo).toBeDefined(); + }); + + env.describe('suite - nested', function() { + env.it('has access to the custom matcher', function() { + expect(env.expect().toFoo).toBeDefined(); + }); + }); + }); + + env.it('spec - top-level - has access to the custom matcher', function() { + expect(env.expect().toFoo).toBeDefined(); + }); + + await env.execute(); + }); + it('throws an exception if you try to create a spy outside of a runnable', async function() { const obj = { fn: function() {} }; let exception; diff --git a/src/core/TreeRunner.js b/src/core/TreeRunner.js index 8b7c5703..581dc0c5 100644 --- a/src/core/TreeRunner.js +++ b/src/core/TreeRunner.js @@ -47,7 +47,10 @@ getJasmineRequireObj().TreeRunner = function(j$) { _executeSpec(spec, specOverallDone) { const onStart = next => { this.#currentRunableTracker.setCurrentSpec(spec); - this.#runableResources.initForRunable(spec.id, spec.parentSuiteId); + this.#runableResources.initForRunable( + spec.id, + spec.parentSuiteId || this.#executionTree.topSuite.id + ); this.#reportDispatcher.specStarted(spec.result).then(next); }; const resultCallback = (result, next) => { From 53e9bc68d2baab0b6bae405c79b8673c6163b5db Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 29 Oct 2025 19:53:34 -0700 Subject: [PATCH 4/7] Bump version to 5.12.1 --- lib/jasmine-core/jasmine.js | 2 +- package.json | 2 +- release_notes/5.12.1.md | 29 +++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 release_notes/5.12.1.md diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index fdc74dc5..a22dce1b 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -11791,5 +11791,5 @@ getJasmineRequireObj().UserContext = function(j$) { }; getJasmineRequireObj().version = function() { - return '5.12.0'; + return '5.12.1'; }; diff --git a/package.json b/package.json index 649cd867..905d1526 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jasmine-core", "license": "MIT", - "version": "5.12.0", + "version": "5.12.1", "repository": { "type": "git", "url": "https://github.com/jasmine/jasmine.git" diff --git a/release_notes/5.12.1.md b/release_notes/5.12.1.md new file mode 100644 index 00000000..75a30125 --- /dev/null +++ b/release_notes/5.12.1.md @@ -0,0 +1,29 @@ +Jasmine Core 5.12.0 Release Notes + +## Bug fixes + +* Fix custom matchers in top-level specs + * Merges [#2088](https://github.com/jasmine/jasmine/pull/2088) from @bonkevin + +## Supported environments + +This version has been tested in the following environments. + +| Environment | Supported versions | +|-------------|--------------------------------| +| Node | 18.20.5**, 20, 22, 24 | +| Safari | 16**, 17** | +| Chrome | 141* | +| Firefox | 102**, 115**, 128**, 140, 144* | +| Edge | 141* | + +\* Evergreen browser. Each version of Jasmine is tested against the latest +version available at release time.
+\** Supported on a best-effort basis. Support for these versions may be dropped +if it becomes impractical, and bugs affecting only these versions may not be +treated as release blockers. + + +------ + +_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_ From 18d4d3865597ba81caf203261fee44f739cabcd2 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 29 Oct 2025 19:55:00 -0700 Subject: [PATCH 5/7] Fix version number in 5.12.1 release notes --- release_notes/5.12.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release_notes/5.12.1.md b/release_notes/5.12.1.md index 75a30125..4dc84ced 100644 --- a/release_notes/5.12.1.md +++ b/release_notes/5.12.1.md @@ -1,4 +1,4 @@ -Jasmine Core 5.12.0 Release Notes +Jasmine Core 5.12.1 Release Notes ## Bug fixes From ff9feb29d3061b347138f590e4d7f41bddf2f516 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Sat, 1 Nov 2025 14:13:51 -0700 Subject: [PATCH 6/7] Configurable spec/suite filename detection * Adds extraItStackFrames and extraDescribeStackFrames config properties. * Un-deprecates the filename properties of reporter events. * Fixes #2065. --- lib/jasmine-core/jasmine.js | 108 +++++++++++++++++++++++------ spec/core/ConfigurationSpec.js | 26 ++++++- spec/core/EnvSpec.js | 44 +++++++++++- spec/helpers/callerFilenameShim.js | 5 ++ spec/support/jasmine-browser.js | 1 + spec/support/jasmine.json | 1 + src/core/Configuration.js | 35 +++++++++- src/core/Env.js | 31 ++++++--- src/core/Spec.js | 22 ++++-- src/core/Suite.js | 20 ++++-- 10 files changed, 251 insertions(+), 42 deletions(-) create mode 100644 spec/helpers/callerFilenameShim.js diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 90adb387..f281779f 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -886,12 +886,11 @@ getJasmineRequireObj().Spec = function(j$) { * @property {String} description - The description passed to the {@link it} that created this spec. * @property {String} fullName - The full description including all ancestors of this spec. * @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe(). - * @property {String} filename - Deprecated. The name of the file the spec was defined in. + * @property {String} filename - The name of the file the spec was defined in. * Note: The value may be incorrect if zone.js is installed or * `it`/`fit`/`xit` have been replaced with versions that don't maintain the - * same call stack height as the originals. This property may be removed in - * a future version unless there is enough user interest in keeping it. - * See {@link https://github.com/jasmine/jasmine/issues/2065}. + * same call stack height as the originals. You can fix that by setting + * {@link Configuration#extraItStackFrames}. * @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec. * @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec. * @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec. @@ -1065,7 +1064,20 @@ getJasmineRequireObj().Spec = function(j$) { * @returns {Array.} * @since 5.7.0 */ - getPath: this.getPath.bind(this) + getPath: this.getPath.bind(this), + + /** + * The name of the file the spec was defined in. + * Note: The value may be incorrect if zone.js is installed or + * `it`/`fit`/`xit` have been replaced with versions that don't maintain the + * same call stack height as the originals. You can fix that by setting + * {@link Configuration#extraItStackFrames}. + * @name Spec#filename + * @readonly + * @type {string} + * @since 5.13.0 + */ + filename: this.filename }; } @@ -1140,6 +1152,8 @@ getJasmineRequireObj().Order = function() { }; getJasmineRequireObj().Env = function(j$) { + const DEFAULT_IT_DESCRIBE_STACK_DEPTH = 3; + /** * @class Env * @since 2.0.0 @@ -1734,14 +1748,14 @@ getJasmineRequireObj().Env = function(j$) { this.describe = function(description, definitionFn) { ensureIsNotNested('describe'); - const filename = callerCallerFilename(); + const filename = indirectCallerFilename(describeStackDepth()); return suiteBuilder.describe(description, definitionFn, filename) .metadata; }; this.xdescribe = function(description, definitionFn) { ensureIsNotNested('xdescribe'); - const filename = callerCallerFilename(); + const filename = indirectCallerFilename(describeStackDepth()); return suiteBuilder.xdescribe(description, definitionFn, filename) .metadata; }; @@ -1749,30 +1763,38 @@ getJasmineRequireObj().Env = function(j$) { this.fdescribe = function(description, definitionFn) { ensureIsNotNested('fdescribe'); ensureNonParallel('fdescribe'); - const filename = callerCallerFilename(); + const filename = indirectCallerFilename(describeStackDepth()); return suiteBuilder.fdescribe(description, definitionFn, filename) .metadata; }; this.it = function(description, fn, timeout) { ensureIsNotNested('it'); - const filename = callerCallerFilename(); + const filename = indirectCallerFilename(itStackDepth()); return suiteBuilder.it(description, fn, timeout, filename).metadata; }; this.xit = function(description, fn, timeout) { ensureIsNotNested('xit'); - const filename = callerCallerFilename(); + const filename = indirectCallerFilename(itStackDepth()); return suiteBuilder.xit(description, fn, timeout, filename).metadata; }; this.fit = function(description, fn, timeout) { ensureIsNotNested('fit'); ensureNonParallel('fit'); - const filename = callerCallerFilename(); + const filename = indirectCallerFilename(itStackDepth()); return suiteBuilder.fit(description, fn, timeout, filename).metadata; }; + function itStackDepth() { + return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraItStackFrames; + } + + function describeStackDepth() { + return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraDescribeStackFrames; + } + /** * Get a user-defined property as part of the properties field of {@link SpecResult} * @name Env#getSpecProperty @@ -1958,11 +1980,12 @@ getJasmineRequireObj().Env = function(j$) { }; } - function callerCallerFilename() { + function indirectCallerFilename(depth) { const frames = new j$.StackTrace(new Error()).frames; - // frames[3] should always exist except in Jasmine's own tests, which bypass - // the global it/describe layer, but don't crash if it doesn't. - return frames[3] && frames[3].file; + // The specified frame should always exist except in Jasmine's own tests, + // which bypass the global it/describe layer, but could be absent in case + // of misconfiguration. Don't crash if it's absent. + return frames[depth] && frames[depth].file; } return Env; @@ -3431,7 +3454,30 @@ getJasmineRequireObj().Configuration = function(j$) { * @type Boolean * @default false */ - detectLateRejectionHandling: false + detectLateRejectionHandling: false, + + /** + * The number of extra stack frames inserted by a wrapper around {@link it} + * or by some other local modification. Jasmine uses this to determine the + * filename for {@link SpecStartedEvent} and {@link SpecDoneEvent}. + * @name Configuration#extraItStackFrames + * @since 5.13.0 + * @type number + * @default 0 + */ + extraItStackFrames: 0, + + /** + * The number of extra stack frames inserted by a wrapper around + * {@link describe} or by some other local modification. Jasmine uses this + * to determine the filename for {@link SpecStartedEvent} and + * {@link SpecDoneEvent}. + * @name Configuration#extraDescribeStackFrames + * @since 5.13.0 + * @type number + * @default 0 + */ + extraDescribeStackFrames: 0 }; Object.freeze(defaultConfig); @@ -3487,6 +3533,16 @@ getJasmineRequireObj().Configuration = function(j$) { if (changes.hasOwnProperty('verboseDeprecations')) { this.#values.verboseDeprecations = changes.verboseDeprecations; } + + // 0 is a valid value for both of these, so a truthiness check wouldn't work + if (typeof changes.extraItStackFrames !== 'undefined') { + this.#values.extraItStackFrames = changes.extraItStackFrames; + } + + if (typeof changes.extraDescribeStackFrames !== 'undefined') { + this.#values.extraDescribeStackFrames = + changes.extraDescribeStackFrames; + } } } @@ -10663,12 +10719,11 @@ getJasmineRequireObj().Suite = function(j$) { * @property {String} description - The description text passed to the {@link describe} that made this suite. * @property {String} fullName - The full description including all ancestors of this suite. * @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe(). - * @property {String} filename - Deprecated. The name of the file the suite was defined in. + * @property {String} filename - The name of the file the suite was defined in. * Note: The value may be incorrect if zone.js is installed or * `describe`/`fdescribe`/`xdescribe` have been replaced with versions that - * don't maintain the same call stack height as the originals. This property - * may be removed in a future version unless there is enough user interest - * in keeping it. See {@link https://github.com/jasmine/jasmine/issues/2065}. + * don't maintain the same call stack height as the originals. You can fix + * that by setting {@link Configuration#extraDescribeStackFrames}. * @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite. * @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite. * @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite. @@ -10875,6 +10930,19 @@ getJasmineRequireObj().Suite = function(j$) { * @since 2.0.0 */ this.description = suite.description; + + /** + * The name of the file the suite was defined in. + * Note: The value may be incorrect if zone.js is installed or + * `describe`/`fdescribe`/`xdescribe` have been replaced with versions + * that don't maintain the same call stack height as the originals. You + * can fix that by setting {@link Configuration#extraItStackFrames}. + * @name Suite#filename + * @readonly + * @type {string} + * @since 5.13.0 + */ + this.filename = suite.filename; } /** diff --git a/spec/core/ConfigurationSpec.js b/spec/core/ConfigurationSpec.js index bc548380..3b3d8a91 100644 --- a/spec/core/ConfigurationSpec.js +++ b/spec/core/ConfigurationSpec.js @@ -13,7 +13,9 @@ describe('Configuration', function() { ...standardBooleanKeys, 'seed', 'specFilter', - 'verboseDeprecations' + 'verboseDeprecations', + 'extraItStackFrames', + 'extraDescribeStackFrames' ]; Object.freeze(standardBooleanKeys); Object.freeze(allKeys); @@ -32,6 +34,8 @@ describe('Configuration', function() { expect(subject.forbidDuplicateNames).toEqual(false); expect(subject.verboseDeprecations).toEqual(false); expect(subject.detectLateRejectionHandling).toEqual(false); + expect(subject.extraItStackFrames).toEqual(0); + expect(subject.extraDescribeStackFrames).toEqual(0); }); describe('copy()', function() { @@ -130,5 +134,25 @@ describe('Configuration', function() { subject.update({ seed: null }); expect(subject.seed).toBeNull(); }); + + it('sets extraItStackFrames when not undefined', function() { + const subject = new jasmineUnderTest.Configuration(); + + subject.update({ extraItStackFrames: undefined }); + expect(subject.extraItStackFrames).toEqual(0); + + subject.update({ extraItStackFrames: 100000 }); + expect(subject.extraItStackFrames).toEqual(100000); + }); + + it('sets extraDescribeStackFrames when not undefined', function() { + const subject = new jasmineUnderTest.Configuration(); + + subject.update({ extraDescribeStackFrames: undefined }); + expect(subject.extraDescribeStackFrames).toEqual(0); + + subject.update({ extraDescribeStackFrames: 100000 }); + expect(subject.extraDescribeStackFrames).toEqual(100000); + }); }); }); diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index db258a00..39392ce1 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -95,7 +95,7 @@ describe('Env', function() { }); }); - it('accepts its own current configureation', function() { + it('accepts its own current configuration', function() { env.configure(env.configuration()); }); @@ -198,6 +198,29 @@ describe('Env', function() { expect(innerSuite.parentSuite).toBe(suite); expect(spec.getFullName()).toEqual('outer suite inner suite a spec'); }); + + it('sets the caller filename correctly when extraDescribeStackFrames is not set', function() { + // IIFE is used to match the stack depth when global describe() is called + const suite = (function() { + return env[methodName]('a suite', function() { + env.it('a spec'); + }); + })(); + expect(suite.filename).toMatch(/EnvSpec\.js$/); + }); + + it('sets the caller filename correctly when extraDescribeStackFrames is set', function() { + env.configure({ extraDescribeStackFrames: 2 }); + // IIFE is used to match the stack depth when global describe() is called + const suite = (function() { + return specHelpers.callerFilenameShim(function() { + return env[methodName]('a suite', function() { + env.it('a spec'); + }); + }); + })(); + expect(suite.filename).toMatch(/EnvSpec\.js$/); + }); } describe('#describe', function() { @@ -300,6 +323,25 @@ describe('Env', function() { .not.toEqual(''); expect(spec.pend).toBeFalsy(); }); + + it('sets the caller filename correctly when extraItStackFrames is not set', function() { + // IIFE is used to match the stack depth when global it() is called + const spec = (function() { + return env[methodName]('a spec', function() {}); + })(); + expect(spec.filename).toMatch(/EnvSpec\.js$/); + }); + + it('sets the caller filename correctly when extraItStackFrames is set', function() { + env.configure({ extraItStackFrames: 2 }); + // IIFE is used to match the stack depth when global it() is called + const spec = (function() { + return specHelpers.callerFilenameShim(function() { + return env[methodName]('a spec', function() {}); + }); + })(); + expect(spec.filename).toMatch(/EnvSpec\.js$/); + }); } describe('#it', function() { diff --git a/spec/helpers/callerFilenameShim.js b/spec/helpers/callerFilenameShim.js new file mode 100644 index 00000000..18f4a132 --- /dev/null +++ b/spec/helpers/callerFilenameShim.js @@ -0,0 +1,5 @@ +(function() { + specHelpers.callerFilenameShim = function(fn) { + return fn(); + }; +})(); diff --git a/spec/support/jasmine-browser.js b/spec/support/jasmine-browser.js index dd24f3eb..09ffdf9b 100644 --- a/spec/support/jasmine-browser.js +++ b/spec/support/jasmine-browser.js @@ -23,6 +23,7 @@ module.exports = { 'helpers/BrowserFlags.js', 'helpers/domHelpers.js', 'helpers/integrationMatchers.js', + 'helpers/callerFilenameShim.js', 'helpers/defineJasmineUnderTest.js', 'helpers/resetEnv.js' ], diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json index a2018b66..e9094d74 100644 --- a/spec/support/jasmine.json +++ b/spec/support/jasmine.json @@ -8,6 +8,7 @@ "helpers/init.js", "helpers/domHelpers.js", "helpers/integrationMatchers.js", + "helpers/callerFilenameShim.js", "helpers/overrideConsoleLogForCircleCi.js", "helpers/nodeDefineJasmineUnderTest.js", "helpers/resetEnv.js" diff --git a/src/core/Configuration.js b/src/core/Configuration.js index 4d32d5ce..1c13839d 100644 --- a/src/core/Configuration.js +++ b/src/core/Configuration.js @@ -123,7 +123,30 @@ getJasmineRequireObj().Configuration = function(j$) { * @type Boolean * @default false */ - detectLateRejectionHandling: false + detectLateRejectionHandling: false, + + /** + * The number of extra stack frames inserted by a wrapper around {@link it} + * or by some other local modification. Jasmine uses this to determine the + * filename for {@link SpecStartedEvent} and {@link SpecDoneEvent}. + * @name Configuration#extraItStackFrames + * @since 5.13.0 + * @type number + * @default 0 + */ + extraItStackFrames: 0, + + /** + * The number of extra stack frames inserted by a wrapper around + * {@link describe} or by some other local modification. Jasmine uses this + * to determine the filename for {@link SpecStartedEvent} and + * {@link SpecDoneEvent}. + * @name Configuration#extraDescribeStackFrames + * @since 5.13.0 + * @type number + * @default 0 + */ + extraDescribeStackFrames: 0 }; Object.freeze(defaultConfig); @@ -179,6 +202,16 @@ getJasmineRequireObj().Configuration = function(j$) { if (changes.hasOwnProperty('verboseDeprecations')) { this.#values.verboseDeprecations = changes.verboseDeprecations; } + + // 0 is a valid value for both of these, so a truthiness check wouldn't work + if (typeof changes.extraItStackFrames !== 'undefined') { + this.#values.extraItStackFrames = changes.extraItStackFrames; + } + + if (typeof changes.extraDescribeStackFrames !== 'undefined') { + this.#values.extraDescribeStackFrames = + changes.extraDescribeStackFrames; + } } } diff --git a/src/core/Env.js b/src/core/Env.js index 701b4586..cb89128b 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -1,4 +1,6 @@ getJasmineRequireObj().Env = function(j$) { + const DEFAULT_IT_DESCRIBE_STACK_DEPTH = 3; + /** * @class Env * @since 2.0.0 @@ -593,14 +595,14 @@ getJasmineRequireObj().Env = function(j$) { this.describe = function(description, definitionFn) { ensureIsNotNested('describe'); - const filename = callerCallerFilename(); + const filename = indirectCallerFilename(describeStackDepth()); return suiteBuilder.describe(description, definitionFn, filename) .metadata; }; this.xdescribe = function(description, definitionFn) { ensureIsNotNested('xdescribe'); - const filename = callerCallerFilename(); + const filename = indirectCallerFilename(describeStackDepth()); return suiteBuilder.xdescribe(description, definitionFn, filename) .metadata; }; @@ -608,30 +610,38 @@ getJasmineRequireObj().Env = function(j$) { this.fdescribe = function(description, definitionFn) { ensureIsNotNested('fdescribe'); ensureNonParallel('fdescribe'); - const filename = callerCallerFilename(); + const filename = indirectCallerFilename(describeStackDepth()); return suiteBuilder.fdescribe(description, definitionFn, filename) .metadata; }; this.it = function(description, fn, timeout) { ensureIsNotNested('it'); - const filename = callerCallerFilename(); + const filename = indirectCallerFilename(itStackDepth()); return suiteBuilder.it(description, fn, timeout, filename).metadata; }; this.xit = function(description, fn, timeout) { ensureIsNotNested('xit'); - const filename = callerCallerFilename(); + const filename = indirectCallerFilename(itStackDepth()); return suiteBuilder.xit(description, fn, timeout, filename).metadata; }; this.fit = function(description, fn, timeout) { ensureIsNotNested('fit'); ensureNonParallel('fit'); - const filename = callerCallerFilename(); + const filename = indirectCallerFilename(itStackDepth()); return suiteBuilder.fit(description, fn, timeout, filename).metadata; }; + function itStackDepth() { + return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraItStackFrames; + } + + function describeStackDepth() { + return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraDescribeStackFrames; + } + /** * Get a user-defined property as part of the properties field of {@link SpecResult} * @name Env#getSpecProperty @@ -817,11 +827,12 @@ getJasmineRequireObj().Env = function(j$) { }; } - function callerCallerFilename() { + function indirectCallerFilename(depth) { const frames = new j$.StackTrace(new Error()).frames; - // frames[3] should always exist except in Jasmine's own tests, which bypass - // the global it/describe layer, but don't crash if it doesn't. - return frames[3] && frames[3].file; + // The specified frame should always exist except in Jasmine's own tests, + // which bypass the global it/describe layer, but could be absent in case + // of misconfiguration. Don't crash if it's absent. + return frames[depth] && frames[depth].file; } return Env; diff --git a/src/core/Spec.js b/src/core/Spec.js index 9d13d757..e97c919d 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -99,12 +99,11 @@ getJasmineRequireObj().Spec = function(j$) { * @property {String} description - The description passed to the {@link it} that created this spec. * @property {String} fullName - The full description including all ancestors of this spec. * @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe(). - * @property {String} filename - Deprecated. The name of the file the spec was defined in. + * @property {String} filename - The name of the file the spec was defined in. * Note: The value may be incorrect if zone.js is installed or * `it`/`fit`/`xit` have been replaced with versions that don't maintain the - * same call stack height as the originals. This property may be removed in - * a future version unless there is enough user interest in keeping it. - * See {@link https://github.com/jasmine/jasmine/issues/2065}. + * same call stack height as the originals. You can fix that by setting + * {@link Configuration#extraItStackFrames}. * @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec. * @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec. * @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec. @@ -278,7 +277,20 @@ getJasmineRequireObj().Spec = function(j$) { * @returns {Array.} * @since 5.7.0 */ - getPath: this.getPath.bind(this) + getPath: this.getPath.bind(this), + + /** + * The name of the file the spec was defined in. + * Note: The value may be incorrect if zone.js is installed or + * `it`/`fit`/`xit` have been replaced with versions that don't maintain the + * same call stack height as the originals. You can fix that by setting + * {@link Configuration#extraItStackFrames}. + * @name Spec#filename + * @readonly + * @type {string} + * @since 5.13.0 + */ + filename: this.filename }; } diff --git a/src/core/Suite.js b/src/core/Suite.js index e8cc044c..55423ddb 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -101,12 +101,11 @@ getJasmineRequireObj().Suite = function(j$) { * @property {String} description - The description text passed to the {@link describe} that made this suite. * @property {String} fullName - The full description including all ancestors of this suite. * @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe(). - * @property {String} filename - Deprecated. The name of the file the suite was defined in. + * @property {String} filename - The name of the file the suite was defined in. * Note: The value may be incorrect if zone.js is installed or * `describe`/`fdescribe`/`xdescribe` have been replaced with versions that - * don't maintain the same call stack height as the originals. This property - * may be removed in a future version unless there is enough user interest - * in keeping it. See {@link https://github.com/jasmine/jasmine/issues/2065}. + * don't maintain the same call stack height as the originals. You can fix + * that by setting {@link Configuration#extraDescribeStackFrames}. * @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite. * @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite. * @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite. @@ -313,6 +312,19 @@ getJasmineRequireObj().Suite = function(j$) { * @since 2.0.0 */ this.description = suite.description; + + /** + * The name of the file the suite was defined in. + * Note: The value may be incorrect if zone.js is installed or + * `describe`/`fdescribe`/`xdescribe` have been replaced with versions + * that don't maintain the same call stack height as the originals. You + * can fix that by setting {@link Configuration#extraItStackFrames}. + * @name Suite#filename + * @readonly + * @type {string} + * @since 5.13.0 + */ + this.filename = suite.filename; } /** From 9a9d3994dacb131c6ff416b044b4e306edc6e877 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Mon, 3 Nov 2025 07:37:11 -0800 Subject: [PATCH 7/7] Add Safari 26 to supported browsers --- .circleci/config.yml | 4 ++-- .github/workflows/safari.yml | 21 +++++++++++++++++++ README.md | 2 +- RELEASE.md | 15 ++++++++++--- .../{run-all-browsers => run-sauce-browsers} | 5 +++++ spec/core/EnvSpec.js | 1 - 6 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/safari.yml rename scripts/{run-all-browsers => run-sauce-browsers} (80%) diff --git a/.circleci/config.yml b/.circleci/config.yml index fe0b2b31..fce54861 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,5 +1,5 @@ # Run tests against supported Node versions, and (except for pull requests) -# against supported browsers. +# against supported browsers that are available on Saucelabs. version: 2.1 @@ -93,7 +93,7 @@ jobs: export SAUCE_TUNNEL_NAME=$CIRCLE_WORKFLOW_JOB_ID scripts/start-sauce-connect set +o errexit - scripts/run-all-browsers + scripts/run-sauce-browsers exitcode=$? set -o errexit scripts/stop-sauce-connect diff --git a/.github/workflows/safari.yml b/.github/workflows/safari.yml new file mode 100644 index 00000000..d22ae3a6 --- /dev/null +++ b/.github/workflows/safari.yml @@ -0,0 +1,21 @@ +name: Test in latest available Safari + +on: + push: + pull_request: + +jobs: + build: + runs-on: macos-latest + + steps: + - name: Report Safari version + run: osascript -e 'get version of application "Safari"' + - uses: actions/checkout@v4 + - name: Use Node.js 22.x + uses: actions/setup-node@v4 + with: + node-version: 22.x + - run: npm install + - run: npm run build + - run: JASMINE_BROWSER=safari npm run ci diff --git a/README.md b/README.md index d61e6b74..64f297e6 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Microsoft Edge) as well as Node. | Environment | Supported versions | |-------------------|----------------------------------| | Node | 18.20.5+*, 20, 22, 24 | -| Safari | 16*, 17* | +| Safari | 16*, 17*, 26* | | Chrome | Evergreen | | Firefox | Evergreen, 102*, 115*, 128*, 140 | | Edge | Evergreen | diff --git a/RELEASE.md b/RELEASE.md index 632a75ee..28c81ee8 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -28,9 +28,18 @@ should also rev to that version. When ready to release - specs are all green and the stories are done: -1. Update the release notes in `release_notes` - use the Anchorman gem to generate the markdown file and edit accordingly. Include a list of supported environments. -1. Update the version in `package.json` -1. Run `npm run build`. +1. Update the release notes in `release_notes` - use the Anchorman gem to + generate the Markdown file and edit accordingly. Include a list of supported + environments. Get that information from these places: + * For Node, see .circleci/config.yml or the README. + * For Firefox ESR and Safari <=17, see scripts/run-sauce-browsers or the README. + * For evergreen browsers, trigger a Circle CI run and check the + [Saucelabs dashboard](https://app.saucelabs.com/dashboard/tests?ownerId=90a771d55857492da3bd5251a2d92457&ownerType=user&ownerName=jasmine-js&start=last7days) + once it's finished. + * For Safari >17, trigger the [Safari action](https://github.com/jasmine/jasmine/actions/workflows/safari.yml) + and get the version from the output. +2. Update the version in `package.json` +3. Run `npm run build`. ### Commit and push core changes diff --git a/scripts/run-all-browsers b/scripts/run-sauce-browsers similarity index 80% rename from scripts/run-all-browsers rename to scripts/run-sauce-browsers index f28619a0..fc6abd7b 100755 --- a/scripts/run-all-browsers +++ b/scripts/run-sauce-browsers @@ -1,5 +1,10 @@ #!/bin/sh +# Run tests in supported browsers that are available on Saucelabs. +# Note: The latest Safari version is tested via GitHub Actions because Saucelabs +# only makes it available to paid enterprise accounts. See +# .github/workflows/safari.yml. + run_browser() { browser=$1 version=$2 diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index 39392ce1..5075817a 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -1,4 +1,3 @@ -// TODO: Fix these unit tests! describe('Env', function() { let env; beforeEach(function() {