diff --git a/.circleci/config.yml b/.circleci/config.yml index 7c06d379..1498ecb2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -192,7 +192,7 @@ workflows: filters: branches: only: - - main + - browser-flakes jobs: - build: executor: node14 diff --git a/Gruntfile.js b/Gruntfile.js index 5891fd61..6d65e5e3 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -34,17 +34,11 @@ module.exports = function(grunt) { jasmine = new Jasmine({jasmineCore: jasmineCore}); jasmine.loadConfigFile('./spec/support/jasmine.json'); + jasmine.onComplete(function(passed) { + done(passed); + }); - jasmine.exitOnCompletion = false; - jasmine.execute().then( - result => { - done(result.overallStatus === 'passed'); - }, - err => { - console.error(err); - exit(1); - } - ); + jasmine.execute(); } ); diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index b3eb4d60..c88a8725 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -169,6 +169,7 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { * Maximum object depth the pretty printer will print to. * Set this to a lower value to speed up pretty printing if you have large objects. * @name jasmine.MAX_PRETTY_PRINT_DEPTH + * @default 8 * @since 1.3.0 */ j$.MAX_PRETTY_PRINT_DEPTH = 8; @@ -177,6 +178,7 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { * This will also limit the number of keys and values displayed for an object. * Elements past this number will be ellipised. * @name jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH + * @default 50 * @since 2.7.0 */ j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 50; @@ -184,15 +186,35 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { * Maximum number of characters to display when pretty printing objects. * Characters past this number will be ellipised. * @name jasmine.MAX_PRETTY_PRINT_CHARS + * @default 100 * @since 2.9.0 */ j$.MAX_PRETTY_PRINT_CHARS = 1000; /** - * Default number of milliseconds Jasmine will wait for an asynchronous spec to complete. + * Default number of milliseconds Jasmine will wait for an asynchronous spec, + * before, or after function to complete. This can be overridden on a case by + * case basis by passing a time limit as the third argument to {@link it}, + * {@link beforeEach}, {@link afterEach}, {@link beforeAll}, or + * {@link afterAll}. The value must be no greater than the largest number of + * milliseconds supported by setTimeout, which is usually 2147483647. + * + * While debugging tests, you may want to set this to a large number (or pass + * a large number to one of the functions mentioned above) so that Jasmine + * does not move on to after functions or the next spec while you're debugging. * @name jasmine.DEFAULT_TIMEOUT_INTERVAL + * @default 5000 * @since 1.3.0 */ - j$.DEFAULT_TIMEOUT_INTERVAL = 5000; + var DEFAULT_TIMEOUT_INTERVAL = 5000; + Object.defineProperty(j$, 'DEFAULT_TIMEOUT_INTERVAL', { + get: function() { + return DEFAULT_TIMEOUT_INTERVAL; + }, + set: function(newValue) { + j$.util.validateTimeout(newValue, 'jasmine.DEFAULT_TIMEOUT_INTERVAL'); + DEFAULT_TIMEOUT_INTERVAL = newValue; + } + }); j$.getGlobal = function() { return jasmineGlobal; @@ -668,6 +690,21 @@ getJasmineRequireObj().util = function(j$) { StopIteration.prototype = Object.create(Error.prototype); StopIteration.prototype.constructor = StopIteration; + util.validateTimeout = function(timeout, msgPrefix) { + // Timeouts are implemented with setTimeout, which only supports a limited + // range of values. The limit is unspecified, as is the behavior when it's + // exceeded. But on all currently supported JS runtimes, setTimeout calls + // the callback immediately when the timeout is greater than 2147483647 + // (the maximum value of a signed 32 bit integer). + var max = 2147483647; + + if (timeout > max) { + throw new Error( + (msgPrefix || 'Timeout value') + ' cannot be greater than ' + max + ); + } + }; + return util; }; @@ -2101,6 +2138,11 @@ getJasmineRequireObj().Env = function(j$) { if (arguments.length > 1 && typeof fn !== 'undefined') { ensureIsFunctionOrAsync(fn, 'it'); } + + if (timeout) { + j$.util.validateTimeout(timeout); + } + var spec = specFactory(description, fn, currentDeclarationSuite, timeout); if (currentDeclarationSuite.markedPending) { spec.pend(); @@ -2130,6 +2172,10 @@ getJasmineRequireObj().Env = function(j$) { this.fit = function(description, fn, timeout) { ensureIsNotNested('fit'); ensureIsFunctionOrAsync(fn, 'fit'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } var spec = specFactory(description, fn, currentDeclarationSuite, timeout); currentDeclarationSuite.addChild(spec); focusedRunnables.push(spec.id); @@ -2194,6 +2240,11 @@ getJasmineRequireObj().Env = function(j$) { this.beforeEach = function(beforeEachFunction, timeout) { ensureIsNotNested('beforeEach'); ensureIsFunctionOrAsync(beforeEachFunction, 'beforeEach'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + currentDeclarationSuite.beforeEach({ fn: beforeEachFunction, timeout: timeout || 0 @@ -2203,6 +2254,11 @@ getJasmineRequireObj().Env = function(j$) { this.beforeAll = function(beforeAllFunction, timeout) { ensureIsNotNested('beforeAll'); ensureIsFunctionOrAsync(beforeAllFunction, 'beforeAll'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + currentDeclarationSuite.beforeAll({ fn: beforeAllFunction, timeout: timeout || 0 @@ -2212,6 +2268,11 @@ getJasmineRequireObj().Env = function(j$) { this.afterEach = function(afterEachFunction, timeout) { ensureIsNotNested('afterEach'); ensureIsFunctionOrAsync(afterEachFunction, 'afterEach'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + afterEachFunction.isCleanup = true; currentDeclarationSuite.afterEach({ fn: afterEachFunction, @@ -2222,6 +2283,11 @@ getJasmineRequireObj().Env = function(j$) { this.afterAll = function(afterAllFunction, timeout) { ensureIsNotNested('afterAll'); ensureIsFunctionOrAsync(afterAllFunction, 'afterAll'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + currentDeclarationSuite.afterAll({ fn: afterAllFunction, timeout: timeout || 0 diff --git a/package.json b/package.json index 49566a8a..9edf722e 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "grunt-contrib-concat": "^1.0.1", "grunt-css-url-embed": "^1.11.1", "grunt-sass": "^3.0.2", - "jasmine": "github:jasmine/jasmine-npm#main", + "jasmine": "^3.4.0", "jasmine-browser-runner": "github:jasmine/jasmine-browser#main", "jsdom": "^15.0.0", "load-grunt-tasks": "^4.0.0", diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index c90d7399..8489e956 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -295,6 +295,12 @@ describe('Env', function() { env.it('async', async function() {}); }).not.toThrow(); }); + + it('throws an error when the timeout value is too large for setTimeout', function() { + expect(function() { + env.it('huge timeout', function() {}, 2147483648); + }).toThrowError('Timeout value cannot be greater than 2147483647'); + }); }); describe('#xit', function() { @@ -340,6 +346,12 @@ describe('Env', function() { /fit expects a function argument; received \[object (Undefined|DOMWindow|Object)\]/ ); }); + + it('throws an error when the timeout value is too large for setTimeout', function() { + expect(function() { + env.fit('huge timeout', function() {}, 2147483648); + }).toThrowError('Timeout value cannot be greater than 2147483647'); + }); }); describe('#beforeEach', function() { @@ -356,6 +368,12 @@ describe('Env', function() { env.beforeEach(async function() {}); }).not.toThrow(); }); + + it('throws an error when the timeout value is too large for setTimeout', function() { + expect(function() { + env.beforeEach(function() {}, 2147483648); + }).toThrowError('Timeout value cannot be greater than 2147483647'); + }); }); describe('#beforeAll', function() { @@ -372,6 +390,12 @@ describe('Env', function() { env.beforeAll(async function() {}); }).not.toThrow(); }); + + it('throws an error when the timeout value is too large for setTimeout', function() { + expect(function() { + env.beforeAll(function() {}, 2147483648); + }).toThrowError('Timeout value cannot be greater than 2147483647'); + }); }); describe('#afterEach', function() { @@ -388,6 +412,12 @@ describe('Env', function() { env.afterEach(async function() {}); }).not.toThrow(); }); + + it('throws an error when the timeout value is too large for setTimeout', function() { + expect(function() { + env.afterEach(function() {}, 2147483648); + }).toThrowError('Timeout value cannot be greater than 2147483647'); + }); }); describe('#afterAll', function() { @@ -404,6 +434,12 @@ describe('Env', function() { env.afterAll(async function() {}); }).not.toThrow(); }); + + it('throws an error when the timeout value is too large for setTimeout', function() { + expect(function() { + env.afterAll(function() {}, 2147483648); + }).toThrowError('Timeout value cannot be greater than 2147483647'); + }); }); describe('when not constructed with suppressLoadErrors: true', function() { diff --git a/spec/core/baseSpec.js b/spec/core/baseSpec.js index 7cb8230d..67e264a5 100644 --- a/spec/core/baseSpec.js +++ b/spec/core/baseSpec.js @@ -153,4 +153,49 @@ describe('base helpers', function() { ); }); }); + + describe('DEFAULT_TIMEOUT_INTERVAL setter', function() { + var max = 2147483647; + + beforeEach(function() { + this.initialValue = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL; + }); + + afterEach(function() { + jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = this.initialValue; + }); + + it('accepts only values <= ' + max, function() { + expect(function() { + jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = max + 1; + }).toThrowError( + 'jasmine.DEFAULT_TIMEOUT_INTERVAL cannot be greater than ' + max + ); + + jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = max; + expect(jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL).toEqual(max); + }); + + it('is consistent with setTimeout in this environment', function(done) { + var f1 = jasmine.createSpy('setTimeout callback for ' + max), + f2 = jasmine.createSpy('setTimeout callback for ' + (max + 1)), + id; + + // Suppress printing of TimeoutOverflowWarning in node + spyOn(console, 'error'); + + id = setTimeout(f1, max); + setTimeout(function() { + clearTimeout(id); + expect(f1).not.toHaveBeenCalled(); + + id = setTimeout(f2, max + 1); + setTimeout(function() { + clearTimeout(id); + expect(f2).toHaveBeenCalled(); + done(); + }); + }); + }); + }); }); diff --git a/src/core/Env.js b/src/core/Env.js index d1c92194..278162a8 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -1069,6 +1069,11 @@ getJasmineRequireObj().Env = function(j$) { if (arguments.length > 1 && typeof fn !== 'undefined') { ensureIsFunctionOrAsync(fn, 'it'); } + + if (timeout) { + j$.util.validateTimeout(timeout); + } + var spec = specFactory(description, fn, currentDeclarationSuite, timeout); if (currentDeclarationSuite.markedPending) { spec.pend(); @@ -1098,6 +1103,10 @@ getJasmineRequireObj().Env = function(j$) { this.fit = function(description, fn, timeout) { ensureIsNotNested('fit'); ensureIsFunctionOrAsync(fn, 'fit'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } var spec = specFactory(description, fn, currentDeclarationSuite, timeout); currentDeclarationSuite.addChild(spec); focusedRunnables.push(spec.id); @@ -1162,6 +1171,11 @@ getJasmineRequireObj().Env = function(j$) { this.beforeEach = function(beforeEachFunction, timeout) { ensureIsNotNested('beforeEach'); ensureIsFunctionOrAsync(beforeEachFunction, 'beforeEach'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + currentDeclarationSuite.beforeEach({ fn: beforeEachFunction, timeout: timeout || 0 @@ -1171,6 +1185,11 @@ getJasmineRequireObj().Env = function(j$) { this.beforeAll = function(beforeAllFunction, timeout) { ensureIsNotNested('beforeAll'); ensureIsFunctionOrAsync(beforeAllFunction, 'beforeAll'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + currentDeclarationSuite.beforeAll({ fn: beforeAllFunction, timeout: timeout || 0 @@ -1180,6 +1199,11 @@ getJasmineRequireObj().Env = function(j$) { this.afterEach = function(afterEachFunction, timeout) { ensureIsNotNested('afterEach'); ensureIsFunctionOrAsync(afterEachFunction, 'afterEach'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + afterEachFunction.isCleanup = true; currentDeclarationSuite.afterEach({ fn: afterEachFunction, @@ -1190,6 +1214,11 @@ getJasmineRequireObj().Env = function(j$) { this.afterAll = function(afterAllFunction, timeout) { ensureIsNotNested('afterAll'); ensureIsFunctionOrAsync(afterAllFunction, 'afterAll'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + currentDeclarationSuite.afterAll({ fn: afterAllFunction, timeout: timeout || 0 diff --git a/src/core/base.js b/src/core/base.js index 1e52ebc3..aeef38b1 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -7,6 +7,7 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { * Maximum object depth the pretty printer will print to. * Set this to a lower value to speed up pretty printing if you have large objects. * @name jasmine.MAX_PRETTY_PRINT_DEPTH + * @default 8 * @since 1.3.0 */ j$.MAX_PRETTY_PRINT_DEPTH = 8; @@ -15,6 +16,7 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { * This will also limit the number of keys and values displayed for an object. * Elements past this number will be ellipised. * @name jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH + * @default 50 * @since 2.7.0 */ j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 50; @@ -22,15 +24,35 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { * Maximum number of characters to display when pretty printing objects. * Characters past this number will be ellipised. * @name jasmine.MAX_PRETTY_PRINT_CHARS + * @default 100 * @since 2.9.0 */ j$.MAX_PRETTY_PRINT_CHARS = 1000; /** - * Default number of milliseconds Jasmine will wait for an asynchronous spec to complete. + * Default number of milliseconds Jasmine will wait for an asynchronous spec, + * before, or after function to complete. This can be overridden on a case by + * case basis by passing a time limit as the third argument to {@link it}, + * {@link beforeEach}, {@link afterEach}, {@link beforeAll}, or + * {@link afterAll}. The value must be no greater than the largest number of + * milliseconds supported by setTimeout, which is usually 2147483647. + * + * While debugging tests, you may want to set this to a large number (or pass + * a large number to one of the functions mentioned above) so that Jasmine + * does not move on to after functions or the next spec while you're debugging. * @name jasmine.DEFAULT_TIMEOUT_INTERVAL + * @default 5000 * @since 1.3.0 */ - j$.DEFAULT_TIMEOUT_INTERVAL = 5000; + var DEFAULT_TIMEOUT_INTERVAL = 5000; + Object.defineProperty(j$, 'DEFAULT_TIMEOUT_INTERVAL', { + get: function() { + return DEFAULT_TIMEOUT_INTERVAL; + }, + set: function(newValue) { + j$.util.validateTimeout(newValue, 'jasmine.DEFAULT_TIMEOUT_INTERVAL'); + DEFAULT_TIMEOUT_INTERVAL = newValue; + } + }); j$.getGlobal = function() { return jasmineGlobal; diff --git a/src/core/util.js b/src/core/util.js index 21c31f58..cd4631f6 100644 --- a/src/core/util.js +++ b/src/core/util.js @@ -127,5 +127,20 @@ getJasmineRequireObj().util = function(j$) { StopIteration.prototype = Object.create(Error.prototype); StopIteration.prototype.constructor = StopIteration; + util.validateTimeout = function(timeout, msgPrefix) { + // Timeouts are implemented with setTimeout, which only supports a limited + // range of values. The limit is unspecified, as is the behavior when it's + // exceeded. But on all currently supported JS runtimes, setTimeout calls + // the callback immediately when the timeout is greater than 2147483647 + // (the maximum value of a signed 32 bit integer). + var max = 2147483647; + + if (timeout > max) { + throw new Error( + (msgPrefix || 'Timeout value') + ' cannot be greater than ' + max + ); + } + }; + return util; };