diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index fe04d065..e382dbbe 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -67,6 +67,8 @@ var getJasmineRequireObj = (function(jasmineGlobal) { j$.Deprecator = jRequire.Deprecator(j$); j$.Env = jRequire.Env(j$); j$.deprecatingThisProxy = jRequire.deprecatingThisProxy(j$); + j$.deprecatingSuiteProxy = jRequire.deprecatingSuiteProxy(j$); + j$.deprecatingSpecProxy = jRequire.deprecatingSpecProxy(j$); j$.StackTrace = jRequire.StackTrace(j$); j$.ExceptionFormatter = jRequire.ExceptionFormatter(j$); j$.ExpectationFilterChain = jRequire.ExpectationFilterChain(); @@ -1738,7 +1740,7 @@ getJasmineRequireObj().Env = function(j$) { * @return {Suite} the root suite */ this.topSuite = function() { - return topSuite; + return j$.deprecatingSuiteProxy(topSuite, null, this); }; /** @@ -3693,6 +3695,176 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) { return DelayedFunctionScheduler; }; +/* eslint-disable compat/compat */ +// TODO: Remove this in the next major release. +getJasmineRequireObj().deprecatingSpecProxy = function(j$) { + function isMember(target, prop) { + return ( + Object.keys(target).indexOf(prop) !== -1 || + Object.keys(j$.Spec.prototype).indexOf(prop) !== -1 + ); + } + + function isAllowedMember(prop) { + return prop === 'description' || prop === 'getFullName'; + } + + function msg(member) { + var memberName = member.toString().replace(/^Symbol\((.+)\)$/, '$1'); + return ( + 'Access to private Spec members (in this case `' + + memberName + + '`) via Env#topSuite is not supported and will break in ' + + 'a future release. See ' + + 'for correct usage.' + ); + } + + try { + new Proxy({}, {}); + } catch (e) { + // Environment does not support Poxy. + return function(spec) { + return spec; + }; + } + + function DeprecatingSpecProxyHandler(env) { + this._env = env; + } + + DeprecatingSpecProxyHandler.prototype.get = function(target, prop, receiver) { + this._maybeDeprecate(target, prop); + + if (prop === 'getFullName') { + // getFullName calls a private method. Re-bind 'this' to avoid a bogus + // deprecation warning. + return target.getFullName.bind(target); + } else { + return target[prop]; + } + }; + + DeprecatingSpecProxyHandler.prototype.set = function(target, prop, value) { + this._maybeDeprecate(target, prop); + return (target[prop] = value); + }; + + DeprecatingSpecProxyHandler.prototype._maybeDeprecate = function( + target, + prop + ) { + if (isMember(target, prop) && !isAllowedMember(prop)) { + this._env.deprecated(msg(prop)); + } + }; + + function deprecatingSpecProxy(spec, env) { + return new Proxy(spec, new DeprecatingSpecProxyHandler(env)); + } + + return deprecatingSpecProxy; +}; + +/* eslint-disable compat/compat */ +// TODO: Remove this in the next major release. +getJasmineRequireObj().deprecatingSuiteProxy = function(j$) { + var allowedMembers = [ + 'children', + 'description', + 'parentSuite', + 'getFullName' + ]; + + function isMember(target, prop) { + return ( + Object.keys(target).indexOf(prop) !== -1 || + Object.keys(j$.Suite.prototype).indexOf(prop) !== -1 + ); + } + + function isAllowedMember(prop) { + return allowedMembers.indexOf(prop) !== -1; + } + + function msg(member) { + var memberName = member.toString().replace(/^Symbol\((.+)\)$/, '$1'); + return ( + 'Access to private Suite members (in this case `' + + memberName + + '`) via Env#topSuite is not supported and will break in ' + + 'a future release. See ' + + 'for correct usage.' + ); + } + try { + new Proxy({}, {}); + } catch (e) { + // Environment does not support Poxy. + return function(suite) { + return suite; + }; + } + + function DeprecatingSuiteProxyHandler(parentSuite, env) { + this._parentSuite = parentSuite; + this._env = env; + } + + DeprecatingSuiteProxyHandler.prototype.get = function( + target, + prop, + receiver + ) { + if (prop === 'children') { + if (!this._children) { + this._children = target.children.map( + this._proxyForChild.bind(this, receiver) + ); + } + + return this._children; + } else if (prop === 'parentSuite') { + return this._parentSuite; + } else { + this._maybeDeprecate(target, prop); + return target[prop]; + } + }; + + DeprecatingSuiteProxyHandler.prototype.set = function(target, prop, value) { + debugger; + this._maybeDeprecate(target, prop); + return (target[prop] = value); + }; + + DeprecatingSuiteProxyHandler.prototype._maybeDeprecate = function( + target, + prop + ) { + if (isMember(target, prop) && !isAllowedMember(prop)) { + this._env.deprecated(msg(prop)); + } + }; + + DeprecatingSuiteProxyHandler.prototype._proxyForChild = function( + ownProxy, + child + ) { + if (child.children) { + return deprecatingSuiteProxy(child, ownProxy, this._env); + } else { + return j$.deprecatingSpecProxy(child, this._env); + } + }; + + function deprecatingSuiteProxy(suite, parentSuite, env) { + return new Proxy(suite, new DeprecatingSuiteProxyHandler(parentSuite, env)); + } + + return deprecatingSuiteProxy; +}; + /* eslint-disable compat/compat */ // TODO: Remove this in the next major release. getJasmineRequireObj().deprecatingThisProxy = function(j$) { diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index 70bb24f8..09949c22 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -26,9 +26,141 @@ describe('Env', function() { }); describe('#topSuite', function() { - it('returns the Jasmine top suite for users to traverse the spec tree', function() { - var suite = env.topSuite(); + it('returns an object that describes the tree of suites and specs', function() { + var suite; + spyOn(env, 'deprecated'); + + env.it('a top level spec'); + env.describe('a suite', function() { + env.it('a spec'); + env.describe('a nested suite', function() { + env.it('a nested spec'); + }); + }); + + suite = env.topSuite(); expect(suite.description).toEqual('Jasmine__TopLevel__Suite'); + expect(suite.getFullName()).toEqual(''); + expect(suite.children.length).toEqual(2); + + expect(suite.children[0].description).toEqual('a top level spec'); + expect(suite.children[0].getFullName()).toEqual('a top level spec'); + expect(suite.children[0].children).toBeFalsy(); + + expect(suite.children[1].description).toEqual('a suite'); + expect(suite.children[1].getFullName()).toEqual('a suite'); + expect(suite.children[1].parentSuite).toBe(suite); + expect(suite.children[1].children.length).toEqual(2); + + expect(suite.children[1].children[0].description).toEqual('a spec'); + expect(suite.children[1].children[0].getFullName()).toEqual( + 'a suite a spec' + ); + expect(suite.children[1].children[0].children).toBeFalsy(); + + expect(suite.children[1].children[1].description).toEqual( + 'a nested suite' + ); + expect(suite.children[1].children[1].getFullName()).toEqual( + 'a suite a nested suite' + ); + expect(suite.children[1].children[1].parentSuite).toBe(suite.children[1]); + expect(suite.children[1].children[1].children.length).toEqual(1); + + expect(suite.children[1].children[1].children[0].description).toEqual( + 'a nested spec' + ); + expect(suite.children[1].children[1].children[0].getFullName()).toEqual( + 'a suite a nested suite a nested spec' + ); + expect(suite.children[1].children[1].children[0].children).toBeFalsy(); + }); + + it('does not deprecate access to public Suite and Spec members', function() { + jasmine.getEnv().requireProxy(); + var suite; + spyOn(env, 'deprecated'); + + env.it('a top level spec'); + env.describe('a suite', function() { + env.it('a spec'); + }); + + suite = env.topSuite(); + suite.description; + suite.getFullName(); + suite.children; + suite.parentSuite; + suite.children[0].description; + suite.children[0].getFullName(); + suite.children[0].children; + + suite.children[1].description; + suite.children[1].getFullName(); + suite.children[1].parentSuite; + suite.children[1].children; + + expect(env.deprecated).not.toHaveBeenCalled(); + }); + + it('deprecates access to internal Suite and Spec members', function() { + jasmine.getEnv().requireProxy(); + var topSuite, expectationFactory, spec; + + env.it('a top level spec'); + spyOn(env, 'deprecated'); + topSuite = env.topSuite(); + + topSuite.expectationFactory; + expect(env.deprecated).toHaveBeenCalledWith( + 'Access to private Suite ' + + 'members (in this case `expectationFactory`) via Env#topSuite is ' + + 'not supported and will break in a future release. See ' + + ' for correct usage.' + ); + env.deprecated.calls.reset(); + + topSuite.expectationFactory = expectationFactory; + expect(env.deprecated).toHaveBeenCalledWith( + 'Access to private Suite ' + + 'members (in this case `expectationFactory`) via Env#topSuite is ' + + 'not supported and will break in a future release. See ' + + ' for correct usage.' + ); + + topSuite.status(); + expect(env.deprecated).toHaveBeenCalledWith( + 'Access to private Suite ' + + 'members (in this case `status`) via Env#topSuite is ' + + 'not supported and will break in a future release. See ' + + ' for correct usage.' + ); + + spec = topSuite.children[0]; + spec.pend(); + expect(env.deprecated).toHaveBeenCalledWith( + 'Access to private Spec ' + + 'members (in this case `pend`) via Env#topSuite ' + + 'is not supported and will break in a future release. See ' + + ' for correct usage.' + ); + + expectationFactory = spec.expectationFactory; + expect(env.deprecated).toHaveBeenCalledWith( + 'Access to private Spec ' + + 'members (in this case `expectationFactory`) via Env#topSuite ' + + 'is not supported and will break in a future release. See ' + + ' for correct usage.' + ); + env.deprecated.calls.reset(); + + spec.expectationFactory = expectationFactory; + expect(env.deprecated).toHaveBeenCalledWith( + 'Access to private Spec ' + + 'members (in this case `expectationFactory`) via Env#topSuite ' + + 'is not supported and will break in a future release. See ' + + ' for correct usage.' + ); }); }); diff --git a/src/core/Env.js b/src/core/Env.js index 3e5fa8d2..1d143793 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -734,7 +734,7 @@ getJasmineRequireObj().Env = function(j$) { * @return {Suite} the root suite */ this.topSuite = function() { - return topSuite; + return j$.deprecatingSuiteProxy(topSuite, null, this); }; /** diff --git a/src/core/deprecatingSpecProxy.js b/src/core/deprecatingSpecProxy.js new file mode 100644 index 00000000..c490705f --- /dev/null +++ b/src/core/deprecatingSpecProxy.js @@ -0,0 +1,70 @@ +/* eslint-disable compat/compat */ +// TODO: Remove this in the next major release. +getJasmineRequireObj().deprecatingSpecProxy = function(j$) { + function isMember(target, prop) { + return ( + Object.keys(target).indexOf(prop) !== -1 || + Object.keys(j$.Spec.prototype).indexOf(prop) !== -1 + ); + } + + function isAllowedMember(prop) { + return prop === 'description' || prop === 'getFullName'; + } + + function msg(member) { + var memberName = member.toString().replace(/^Symbol\((.+)\)$/, '$1'); + return ( + 'Access to private Spec members (in this case `' + + memberName + + '`) via Env#topSuite is not supported and will break in ' + + 'a future release. See ' + + 'for correct usage.' + ); + } + + try { + new Proxy({}, {}); + } catch (e) { + // Environment does not support Poxy. + return function(spec) { + return spec; + }; + } + + function DeprecatingSpecProxyHandler(env) { + this._env = env; + } + + DeprecatingSpecProxyHandler.prototype.get = function(target, prop, receiver) { + this._maybeDeprecate(target, prop); + + if (prop === 'getFullName') { + // getFullName calls a private method. Re-bind 'this' to avoid a bogus + // deprecation warning. + return target.getFullName.bind(target); + } else { + return target[prop]; + } + }; + + DeprecatingSpecProxyHandler.prototype.set = function(target, prop, value) { + this._maybeDeprecate(target, prop); + return (target[prop] = value); + }; + + DeprecatingSpecProxyHandler.prototype._maybeDeprecate = function( + target, + prop + ) { + if (isMember(target, prop) && !isAllowedMember(prop)) { + this._env.deprecated(msg(prop)); + } + }; + + function deprecatingSpecProxy(spec, env) { + return new Proxy(spec, new DeprecatingSpecProxyHandler(env)); + } + + return deprecatingSpecProxy; +}; diff --git a/src/core/deprecatingSuiteProxy.js b/src/core/deprecatingSuiteProxy.js new file mode 100644 index 00000000..11759374 --- /dev/null +++ b/src/core/deprecatingSuiteProxy.js @@ -0,0 +1,98 @@ +/* eslint-disable compat/compat */ +// TODO: Remove this in the next major release. +getJasmineRequireObj().deprecatingSuiteProxy = function(j$) { + var allowedMembers = [ + 'children', + 'description', + 'parentSuite', + 'getFullName' + ]; + + function isMember(target, prop) { + return ( + Object.keys(target).indexOf(prop) !== -1 || + Object.keys(j$.Suite.prototype).indexOf(prop) !== -1 + ); + } + + function isAllowedMember(prop) { + return allowedMembers.indexOf(prop) !== -1; + } + + function msg(member) { + var memberName = member.toString().replace(/^Symbol\((.+)\)$/, '$1'); + return ( + 'Access to private Suite members (in this case `' + + memberName + + '`) via Env#topSuite is not supported and will break in ' + + 'a future release. See ' + + 'for correct usage.' + ); + } + try { + new Proxy({}, {}); + } catch (e) { + // Environment does not support Poxy. + return function(suite) { + return suite; + }; + } + + function DeprecatingSuiteProxyHandler(parentSuite, env) { + this._parentSuite = parentSuite; + this._env = env; + } + + DeprecatingSuiteProxyHandler.prototype.get = function( + target, + prop, + receiver + ) { + if (prop === 'children') { + if (!this._children) { + this._children = target.children.map( + this._proxyForChild.bind(this, receiver) + ); + } + + return this._children; + } else if (prop === 'parentSuite') { + return this._parentSuite; + } else { + this._maybeDeprecate(target, prop); + return target[prop]; + } + }; + + DeprecatingSuiteProxyHandler.prototype.set = function(target, prop, value) { + debugger; + this._maybeDeprecate(target, prop); + return (target[prop] = value); + }; + + DeprecatingSuiteProxyHandler.prototype._maybeDeprecate = function( + target, + prop + ) { + if (isMember(target, prop) && !isAllowedMember(prop)) { + this._env.deprecated(msg(prop)); + } + }; + + DeprecatingSuiteProxyHandler.prototype._proxyForChild = function( + ownProxy, + child + ) { + if (child.children) { + return deprecatingSuiteProxy(child, ownProxy, this._env); + } else { + return j$.deprecatingSpecProxy(child, this._env); + } + }; + + function deprecatingSuiteProxy(suite, parentSuite, env) { + return new Proxy(suite, new DeprecatingSuiteProxyHandler(parentSuite, env)); + } + + return deprecatingSuiteProxy; +}; diff --git a/src/core/requireCore.js b/src/core/requireCore.js index e20ad56d..80de1e28 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -45,6 +45,8 @@ var getJasmineRequireObj = (function(jasmineGlobal) { j$.Deprecator = jRequire.Deprecator(j$); j$.Env = jRequire.Env(j$); j$.deprecatingThisProxy = jRequire.deprecatingThisProxy(j$); + j$.deprecatingSuiteProxy = jRequire.deprecatingSuiteProxy(j$); + j$.deprecatingSpecProxy = jRequire.deprecatingSpecProxy(j$); j$.StackTrace = jRequire.StackTrace(j$); j$.ExceptionFormatter = jRequire.ExceptionFormatter(j$); j$.ExpectationFilterChain = jRequire.ExpectationFilterChain();