diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index b97e13f8..a15ad5ee 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -66,6 +66,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) { j$.getClearStack = jRequire.clearStack(j$); j$.Clock = jRequire.Clock(); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$); + j$.deprecatingSpecProxy = jRequire.deprecatingSpecProxy(j$); j$.Deprecator = jRequire.Deprecator(j$); j$.Configuration = jRequire.Configuration(j$); j$.Env = jRequire.Env(j$); @@ -1498,7 +1499,8 @@ getJasmineRequireObj().Env = function(j$) { runQueue, TreeProcessor: j$.TreeProcessor, globalErrors, - getConfig: () => config + getConfig: () => config, + deprecated: this.deprecated }); this.setParallelLoadingState = function(state) { @@ -3711,6 +3713,43 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) { return DelayedFunctionScheduler; }; +// TODO: Remove this in the next major release. +getJasmineRequireObj().deprecatingSpecProxy = function(j$) { + const allowedMembers = ['id', 'description', 'getFullName', 'getPath']; + + function isMember(target, prop) { + return ( + Object.keys(target).indexOf(prop) !== -1 || + Object.keys(j$.Spec.prototype).indexOf(prop) !== -1 + ); + } + + function msg(member) { + const memberName = member.toString().replace(/^Symbol\((.+)\)$/, '$1'); + return ( + 'Access to private Spec members (in this case `' + + memberName + + '`) via spec filters is not supported and will break in ' + + 'a future release. See ' + + 'for correct usage.' + ); + } + + function deprecatingSpecProxy(spec, deprecated) { + return new Proxy(spec, { + get(target, prop, receiver) { + if (isMember(target, prop) && !allowedMembers.includes(prop)) { + deprecated(msg(prop)); + } + + return target[prop]; + } + }); + } + + return deprecatingSpecProxy; +}; + getJasmineRequireObj().Deprecator = function(j$) { function Deprecator(topSuite) { this.topSuite_ = topSuite; @@ -9440,6 +9479,7 @@ getJasmineRequireObj().Runner = function(j$) { #globalErrors; #reportDispatcher; #getConfig; + #deprecated; #executedBefore; #currentRunableTracker; @@ -9453,6 +9493,7 @@ getJasmineRequireObj().Runner = function(j$) { this.#globalErrors = options.globalErrors; this.#reportDispatcher = options.reportDispatcher; this.#getConfig = options.getConfig; + this.#deprecated = options.deprecated; this.#executedBefore = false; this.#currentRunableTracker = new j$.CurrentRunableTracker(); } @@ -9505,8 +9546,9 @@ getJasmineRequireObj().Runner = function(j$) { orderChildren: function(node) { return order.sort(node.children); }, - excludeNode: function(spec) { - return !config.specFilter(spec); + excludeNode: spec => { + const proxy = j$.deprecatingSpecProxy(spec, this.#deprecated); + return !config.specFilter(proxy); } }); this.#executionTree = treeProcessor.processTree(); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index d9224d40..f1b8504d 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -913,6 +913,7 @@ describe('Env integration', function() { }); env.configure({ + random: false, specFilter: function(spec) { return /^first suite/.test(spec.getFullName()); } @@ -920,13 +921,44 @@ describe('Env integration', function() { await env.execute(); - expect(calls.length).toEqual(2); - expect(calls).toEqual( - jasmine.arrayContaining(['first spec', 'second spec']) - ); + expect(calls).toEqual(['first spec', 'second spec']); expect(suiteCallback).toHaveBeenCalled(); }); + it('reports a deprecation warning when a spec filter accesses private properties', async function() { + env.it('a spec', function() {}); + + const reporter = jasmine.createSpyObj('reporter', ['jasmineDone']); + env.addReporter(reporter); + + env.configure({ + random: false, + specFilter: function(spec) { + spec.result; // deprecated + spec.id; // not deprecated + spec.description; // not deprecated + spec.getPath(); // not deprecated + spec.getFullName(); // not deprecated + return true; + } + }); + + spyOn(console, 'error'); + await env.execute(); + + expect(reporter.jasmineDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + deprecationWarnings: [ + jasmine.objectContaining({ + message: jasmine.stringContaining( + 'Access to private Spec members (in this case `result`)' + ) + }) + ] + }) + ); + }); + it('Functions can be spied on and have their calls tracked', async function() { let originalFunctionWasCalled = false; const subject = { diff --git a/src/core/Env.js b/src/core/Env.js index 701b4586..3e4fb741 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -371,7 +371,8 @@ getJasmineRequireObj().Env = function(j$) { runQueue, TreeProcessor: j$.TreeProcessor, globalErrors, - getConfig: () => config + getConfig: () => config, + deprecated: this.deprecated }); this.setParallelLoadingState = function(state) { diff --git a/src/core/Runner.js b/src/core/Runner.js index db44c639..c6d69a7a 100644 --- a/src/core/Runner.js +++ b/src/core/Runner.js @@ -10,6 +10,7 @@ getJasmineRequireObj().Runner = function(j$) { #globalErrors; #reportDispatcher; #getConfig; + #deprecated; #executedBefore; #currentRunableTracker; @@ -23,6 +24,7 @@ getJasmineRequireObj().Runner = function(j$) { this.#globalErrors = options.globalErrors; this.#reportDispatcher = options.reportDispatcher; this.#getConfig = options.getConfig; + this.#deprecated = options.deprecated; this.#executedBefore = false; this.#currentRunableTracker = new j$.CurrentRunableTracker(); } @@ -75,8 +77,9 @@ getJasmineRequireObj().Runner = function(j$) { orderChildren: function(node) { return order.sort(node.children); }, - excludeNode: function(spec) { - return !config.specFilter(spec); + excludeNode: spec => { + const proxy = j$.deprecatingSpecProxy(spec, this.#deprecated); + return !config.specFilter(proxy); } }); this.#executionTree = treeProcessor.processTree(); diff --git a/src/core/deprecatingSpecProxy.js b/src/core/deprecatingSpecProxy.js new file mode 100644 index 00000000..eb1b583e --- /dev/null +++ b/src/core/deprecatingSpecProxy.js @@ -0,0 +1,36 @@ +// TODO: Remove this in the next major release. +getJasmineRequireObj().deprecatingSpecProxy = function(j$) { + const allowedMembers = ['id', 'description', 'getFullName', 'getPath']; + + function isMember(target, prop) { + return ( + Object.keys(target).indexOf(prop) !== -1 || + Object.keys(j$.Spec.prototype).indexOf(prop) !== -1 + ); + } + + function msg(member) { + const memberName = member.toString().replace(/^Symbol\((.+)\)$/, '$1'); + return ( + 'Access to private Spec members (in this case `' + + memberName + + '`) via spec filters is not supported and will break in ' + + 'a future release. See ' + + 'for correct usage.' + ); + } + + function deprecatingSpecProxy(spec, deprecated) { + return new Proxy(spec, { + get(target, prop, receiver) { + if (isMember(target, prop) && !allowedMembers.includes(prop)) { + deprecated(msg(prop)); + } + + return target[prop]; + } + }); + } + + return deprecatingSpecProxy; +}; diff --git a/src/core/requireCore.js b/src/core/requireCore.js index 669bd244..9ce8aed3 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -42,6 +42,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) { j$.getClearStack = jRequire.clearStack(j$); j$.Clock = jRequire.Clock(); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$); + j$.deprecatingSpecProxy = jRequire.deprecatingSpecProxy(j$); j$.Deprecator = jRequire.Deprecator(j$); j$.Configuration = jRequire.Configuration(j$); j$.Env = jRequire.Env(j$);