diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js
index 8539c76c..ca67c293 100644
--- a/lib/jasmine-core/jasmine-html.js
+++ b/lib/jasmine-core/jasmine-html.js
@@ -308,7 +308,8 @@ jasmineRequire.HtmlReporter = function(j$) {
addDeprecationWarnings(doneResult);
for (i = 0; i < deprecationWarnings.length; i++) {
- var context;
+ var children = [],
+ context;
switch (deprecationWarnings[i].runnableType) {
case 'spec':
@@ -321,13 +322,23 @@ jasmineRequire.HtmlReporter = function(j$) {
context = '';
}
+ deprecationWarnings[i].message.split('\n').forEach(function(line) {
+ children.push(line);
+ children.push(createDom('br'));
+ });
+
+ children[0] = 'DEPRECATION: ' + children[0];
+ children.push(context);
+
+ if (deprecationWarnings[i].stack) {
+ children.push(createExpander(deprecationWarnings[i].stack));
+ }
+
alert.appendChild(
createDom(
'span',
{ className: 'jasmine-bar jasmine-warning' },
- 'DEPRECATION: ' + deprecationWarnings[i].message,
- createDom('br'),
- context
+ children
)
);
}
@@ -642,17 +653,44 @@ jasmineRequire.HtmlReporter = function(j$) {
if (result && result.deprecationWarnings) {
for (var i = 0; i < result.deprecationWarnings.length; i++) {
var warning = result.deprecationWarnings[i].message;
- if (!j$.util.arrayContains(warning)) {
- deprecationWarnings.push({
- message: warning,
- runnableName: result.fullName,
- runnableType: runnableType
- });
- }
+ deprecationWarnings.push({
+ message: warning,
+ stack: result.deprecationWarnings[i].stack,
+ runnableName: result.fullName,
+ runnableType: runnableType
+ });
}
}
}
+ function createExpander(stackTrace) {
+ var expandLink = createDom('a', { href: '#' }, 'Show stack trace');
+ var root = createDom(
+ 'div',
+ { className: 'jasmine-expander' },
+ expandLink,
+ createDom(
+ 'div',
+ { className: 'jasmine-expander-contents jasmine-stack-trace' },
+ stackTrace
+ )
+ );
+
+ expandLink.addEventListener('click', function(e) {
+ e.preventDefault();
+
+ if (root.classList.contains('jasmine-expanded')) {
+ root.classList.remove('jasmine-expanded');
+ expandLink.textContent = 'Show stack trace';
+ } else {
+ root.classList.add('jasmine-expanded');
+ expandLink.textContent = 'Hide stack trace';
+ }
+ });
+
+ return root;
+ }
+
function find(selector) {
return getContainer().querySelector('.jasmine_html-reporter ' + selector);
}
@@ -666,11 +704,23 @@ jasmineRequire.HtmlReporter = function(j$) {
}
}
- function createDom(type, attrs, childrenVarArgs) {
- var el = createElement(type);
+ function createDom(type, attrs, childrenArrayOrVarArgs) {
+ var el = createElement(type),
+ children,
+ i;
- for (var i = 2; i < arguments.length; i++) {
- var child = arguments[i];
+ if (j$.isArray_(childrenArrayOrVarArgs)) {
+ children = childrenArrayOrVarArgs;
+ } else {
+ children = [];
+
+ for (i = 2; i < arguments.length; i++) {
+ children.push(arguments[i]);
+ }
+ }
+
+ for (i = 0; i < children.length; i++) {
+ var child = children[i];
if (typeof child === 'string') {
el.appendChild(createTextNode(child));
diff --git a/lib/jasmine-core/jasmine.css b/lib/jasmine-core/jasmine.css
index dba3ca98..18b6457c 100644
--- a/lib/jasmine-core/jasmine.css
+++ b/lib/jasmine-core/jasmine.css
@@ -165,6 +165,8 @@ body {
background-color: #bababa;
}
.jasmine_html-reporter .jasmine-bar.jasmine-warning {
+ margin-top: 14px;
+ margin-bottom: 14px;
background-color: #ba9d37;
color: #333;
}
@@ -268,4 +270,21 @@ body {
border: 1px solid #ddd;
background: white;
white-space: pre;
+}
+.jasmine_html-reporter .jasmine-expander a {
+ display: block;
+ margin-left: 14px;
+ color: blue;
+ text-decoration: underline;
+}
+.jasmine_html-reporter .jasmine-expander-contents {
+ display: none;
+}
+.jasmine_html-reporter .jasmine-expanded {
+ padding-bottom: 10px;
+}
+.jasmine_html-reporter .jasmine-expanded .jasmine-expander-contents {
+ display: block;
+ margin-left: 14px;
+ padding: 5px;
}
\ No newline at end of file
diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js
index 1e4c891c..75c578c5 100644
--- a/lib/jasmine-core/jasmine.js
+++ b/lib/jasmine-core/jasmine.js
@@ -64,7 +64,10 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
j$.getClearStack = jRequire.clearStack(j$);
j$.Clock = jRequire.Clock();
j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$);
+ j$.Deprecator = jRequire.Deprecator(j$);
j$.Env = jRequire.Env(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();
@@ -1001,7 +1004,6 @@ getJasmineRequireObj().Env = function(j$) {
var currentlyExecutingSuites = [];
var currentDeclarationSuite = null;
var hasFailures = false;
- var deprecationsToSuppress = [];
/**
* This represents the available options to configure Jasmine.
@@ -1197,6 +1199,7 @@ getJasmineRequireObj().Env = function(j$) {
if (configuration.hasOwnProperty('verboseDeprecations')) {
config.verboseDeprecations = configuration.verboseDeprecations;
+ deprecator.verboseDeprecations(config.verboseDeprecations);
}
};
@@ -1451,48 +1454,31 @@ getJasmineRequireObj().Env = function(j$) {
return buildExpectationResult(attrs);
};
- this.deprecated = function(deprecation) {
+ /**
+ * Causes a deprecation warning to be logged to the console and reported to
+ * reporters.
+ *
+ * The optional second parameter is an object that can have either of the
+ * following properties:
+ *
+ * omitStackTrace: Whether to omit the stack trace. Optional. Defaults to
+ * false. This option is ignored if the deprecation is an Error. Set this
+ * when the stack trace will not contain anything that helps the user find
+ * the source of the deprecation.
+ *
+ * ignoreRunnable: Whether to log the deprecation on the root suite, ignoring
+ * the spec or suite that's running when it happens. Optional. Defaults to
+ * false.
+ *
+ * @name Env#deprecated
+ * @since 2.99
+ * @function
+ * @param {String|Error} deprecation The deprecation message
+ * @param {Object} [options] Optional extra options, as described above
+ */
+ this.deprecated = function(deprecation, options) {
var runnable = currentRunnable() || topSuite;
- var context;
-
- if (runnable === topSuite) {
- context = '';
- } else if (runnable === currentSuite()) {
- context = ' (in suite: ' + runnable.getFullName() + ')';
- } else {
- context = ' (in spec: ' + runnable.getFullName() + ')';
- }
-
- runnable.addDeprecationWarning(deprecation);
- if (
- typeof console !== 'undefined' &&
- typeof console.error === 'function'
- ) {
- console.error('DEPRECATION: ' + deprecation + context);
- }
- };
-
- this.deprecatedOnceWithStack = function(deprecation) {
- var formatter = new j$.ExceptionFormatter(),
- stackTrace = formatter
- .stack(j$.util.errorWithStack())
- .replace(/^Error\n/m, '');
-
- if (config.verboseDeprecations) {
- this.deprecated(deprecation + '\n' + stackTrace);
- } else {
- if (deprecationsToSuppress.indexOf(deprecation) === -1) {
- this.deprecated(
- deprecation +
- '\n' +
- 'Note: This message will be shown only once. ' +
- 'Set config.verboseDeprecations to true to see every occurrence.\n' +
- stackTrace
- );
- }
-
- deprecationsToSuppress.push(deprecation);
- }
+ deprecator.addDeprecationWarning(runnable, deprecation, options);
};
var queueRunnerFactory = function(options, args) {
@@ -1528,6 +1514,7 @@ getJasmineRequireObj().Env = function(j$) {
asyncExpectationFactory: suiteAsyncExpectationFactory,
expectationResultFactory: expectationResultFactory
});
+ var deprecator = new j$.Deprecator(topSuite);
defaultResourcesForRunnable(topSuite.id);
currentDeclarationSuite = topSuite;
@@ -1539,7 +1526,7 @@ getJasmineRequireObj().Env = function(j$) {
* @return {Suite} the root suite
*/
this.topSuite = function() {
- return topSuite;
+ return j$.deprecatingSuiteProxy(topSuite, null, this);
};
/**
@@ -3367,6 +3354,267 @@ 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;
+};
+
+getJasmineRequireObj().Deprecator = function(j$) {
+ function Deprecator(topSuite) {
+ this.topSuite_ = topSuite;
+ this.verbose_ = false;
+ this.toSuppress_ = [];
+ }
+
+ var verboseNote =
+ 'Note: This message will be shown only once. ' +
+ 'Set config.verboseDeprecations to true to see every occurrence.';
+
+ Deprecator.prototype.verboseDeprecations = function(enabled) {
+ this.verbose_ = enabled;
+ };
+
+ // runnable is a spec or a suite.
+ // deprecation is a string or an Error.
+ // See Env#deprecated for a description of the options argument.
+ Deprecator.prototype.addDeprecationWarning = function(
+ runnable,
+ deprecation,
+ options
+ ) {
+ options = options || {};
+
+ if (!this.verbose_ && !j$.isError_(deprecation)) {
+ if (this.toSuppress_.indexOf(deprecation) !== -1) {
+ return;
+ }
+ this.toSuppress_.push(deprecation);
+ }
+
+ this.log_(runnable, deprecation, options);
+ this.report_(runnable, deprecation, options);
+ };
+
+ Deprecator.prototype.log_ = function(runnable, deprecation, options) {
+ var context;
+
+ if (j$.isError_(deprecation)) {
+ console.error(deprecation);
+ return;
+ }
+
+ if (runnable === this.topSuite_ || options.ignoreRunnable) {
+ context = '';
+ } else if (runnable.children) {
+ context = ' (in suite: ' + runnable.getFullName() + ')';
+ } else {
+ context = ' (in spec: ' + runnable.getFullName() + ')';
+ }
+
+ if (!options.omitStackTrace) {
+ context += '\n' + this.stackTrace_();
+ }
+
+ if (!this.verbose_) {
+ context += '\n' + verboseNote;
+ }
+
+ console.error('DEPRECATION: ' + deprecation + context);
+ };
+
+ Deprecator.prototype.stackTrace_ = function() {
+ var formatter = new j$.ExceptionFormatter();
+ return formatter.stack(j$.util.errorWithStack()).replace(/^Error\n/m, '');
+ };
+
+ Deprecator.prototype.report_ = function(runnable, deprecation, options) {
+ if (options.ignoreRunnable) {
+ runnable = this.topSuite_;
+ }
+
+ if (j$.isError_(deprecation)) {
+ runnable.addDeprecationWarning(deprecation);
+ return;
+ }
+
+ if (!this.verbose_) {
+ deprecation += '\n' + verboseNote;
+ }
+
+ runnable.addDeprecationWarning({
+ message: deprecation,
+ omitStackTrace: options.omitStackTrace || false
+ });
+ };
+
+ return Deprecator;
+};
+
getJasmineRequireObj().errors = function() {
function ExpectationFailed() {}
@@ -3779,7 +4027,7 @@ getJasmineRequireObj().buildExpectationResult = function(j$) {
var result = {
matcherName: options.matcherName,
message: message(),
- stack: stack(),
+ stack: options.omitStackTrace ? '' : stack(),
passed: options.passed
};
@@ -7466,8 +7714,12 @@ getJasmineRequireObj().QueueRunner = function(j$) {
};
QueueRunner.prototype.diagnoseConflictingAsync_ = function(fn, retval) {
+ var msg;
+
if (retval && j$.isFunction_(retval.then)) {
- // Issue a warning that matches the user's code
+ // Issue a warning that matches the user's code.
+ // Omit the stack trace because there's almost certainly no user code
+ // on the stack at this point.
if (j$.isAsyncFunction_(fn)) {
this.onException(
'An asynchronous before/it/after ' +
@@ -7483,6 +7735,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
'function to not return a promise.'
);
}
+
+ this.deprecated(msg, { omitStackTrace: true });
}
};
diff --git a/spec/core/DeprecatorSpec.js b/spec/core/DeprecatorSpec.js
new file mode 100644
index 00000000..8e4bbe75
--- /dev/null
+++ b/spec/core/DeprecatorSpec.js
@@ -0,0 +1,329 @@
+/* eslint no-console: 0 */
+describe('Deprecator', function() {
+ describe('#deprecate', function() {
+ beforeEach(function() {
+ spyOn(console, 'error');
+ });
+
+ it('logs the mesage without context when the runnable is the top suite', function() {
+ var runnable = { addDeprecationWarning: function() {} };
+ var deprecator = new jasmineUnderTest.Deprecator(runnable);
+ deprecator.verboseDeprecations(true);
+
+ deprecator.addDeprecationWarning(runnable, 'the message', {
+ omitStackTrace: true
+ });
+
+ expect(console.error).toHaveBeenCalledWith('DEPRECATION: the message');
+ });
+
+ it('logs the message in a descendant suite', function() {
+ var runnable = {
+ addDeprecationWarning: function() {},
+ getFullName: function() {
+ return 'the suite';
+ },
+ children: []
+ };
+ var deprecator = new jasmineUnderTest.Deprecator({});
+ deprecator.verboseDeprecations(true);
+
+ deprecator.addDeprecationWarning(runnable, 'the message', {
+ omitStackTrace: true
+ });
+
+ expect(console.error).toHaveBeenCalledWith(
+ 'DEPRECATION: the message (in suite: the suite)'
+ );
+ });
+
+ it('logs and reports the message in a spec', function() {
+ var runnable = {
+ addDeprecationWarning: function() {},
+ getFullName: function() {
+ return 'the spec';
+ }
+ };
+ var deprecator = new jasmineUnderTest.Deprecator({});
+ deprecator.verboseDeprecations(true);
+
+ deprecator.addDeprecationWarning(runnable, 'the message', {
+ omitStackTrace: true
+ });
+
+ expect(console.error).toHaveBeenCalledWith(
+ 'DEPRECATION: the message (in spec: the spec)'
+ );
+ });
+
+ it('logs and reports the message without runnable info when ignoreRunnable is true', function() {
+ var topSuite = jasmine.createSpyObj('topSuite', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+ var deprecator = new jasmineUnderTest.Deprecator(topSuite);
+ var runnable = jasmine.createSpyObj('spec', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+ runnable.getFullName.and.returnValue('a spec');
+
+ deprecator.addDeprecationWarning(runnable, 'the message', {
+ ignoreRunnable: true
+ });
+
+ expect(topSuite.addDeprecationWarning).toHaveBeenCalledWith(
+ jasmine.objectContaining({
+ message: jasmine.stringMatching(/^the message/)
+ })
+ );
+ expect(runnable.addDeprecationWarning).not.toHaveBeenCalled();
+ expect(console.error).toHaveBeenCalledWith(
+ jasmine.stringMatching(/the message/)
+ );
+ expect(console.error).not.toHaveBeenCalledWith(
+ jasmine.stringMatching(/a spec/)
+ );
+ });
+
+ describe('with no options', function() {
+ it('includes the stack trace', function() {
+ testStackTrace(undefined);
+ });
+ });
+
+ it('omits the stack trace when omitStackTrace is true', function() {
+ testNoStackTrace({ omitStackTrace: true });
+ });
+
+ it('includes the stack trace when omitStackTrace is false', function() {
+ testStackTrace({ omitStackTrace: false });
+ });
+
+ it('includes the stack trace when omitStackTrace is undefined', function() {
+ testStackTrace({ includeStackTrace: undefined });
+ });
+
+ it('emits the deprecation only once when verboseDeprecations is not set', function() {
+ var deprecator = new jasmineUnderTest.Deprecator({});
+ var runnable1 = jasmine.createSpyObj('runnable1', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+ var runnable2 = jasmine.createSpyObj('runnable2', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+
+ deprecator.addDeprecationWarning(runnable1, 'the message');
+ deprecator.addDeprecationWarning(runnable1, 'the message');
+ deprecator.addDeprecationWarning(runnable2, 'the message');
+
+ expect(runnable1.addDeprecationWarning).toHaveBeenCalledTimes(1);
+ expect(runnable2.addDeprecationWarning).not.toHaveBeenCalled();
+ });
+
+ it('emits the deprecation only once when verboseDeprecations is false', function() {
+ var deprecator = new jasmineUnderTest.Deprecator({});
+ var runnable1 = jasmine.createSpyObj('runnable1', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+ var runnable2 = jasmine.createSpyObj('runnable2', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+
+ deprecator.verboseDeprecations(false);
+ deprecator.addDeprecationWarning(runnable1, 'the message');
+ deprecator.addDeprecationWarning(runnable1, 'the message');
+ deprecator.addDeprecationWarning(runnable2, 'the message');
+
+ expect(runnable1.addDeprecationWarning).toHaveBeenCalledTimes(1);
+ expect(runnable2.addDeprecationWarning).not.toHaveBeenCalled();
+ });
+
+ it('emits the deprecation for each call when verboseDeprecations is true', function() {
+ var deprecator = new jasmineUnderTest.Deprecator({});
+ var runnable1 = jasmine.createSpyObj('runnable1', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+ var runnable2 = jasmine.createSpyObj('runnable2', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+
+ deprecator.verboseDeprecations(true);
+ deprecator.addDeprecationWarning(runnable1, 'the message');
+ deprecator.addDeprecationWarning(runnable1, 'the message');
+ deprecator.addDeprecationWarning(runnable2, 'the message');
+
+ expect(runnable1.addDeprecationWarning).toHaveBeenCalledTimes(2);
+ expect(runnable2.addDeprecationWarning).toHaveBeenCalled();
+ });
+
+ it('includes a note about verboseDeprecations', function() {
+ var deprecator = new jasmineUnderTest.Deprecator({});
+ var runnable = jasmine.createSpyObj('runnable', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+
+ deprecator.addDeprecationWarning(runnable, 'the message');
+
+ expect(runnable.addDeprecationWarning).toHaveBeenCalledTimes(1);
+ expect(
+ runnable.addDeprecationWarning.calls.argsFor(0)[0].message
+ ).toContain(verboseDeprecationsNote());
+ expect(console.error).toHaveBeenCalledTimes(1);
+ expect(console.error.calls.argsFor(0)[0]).toContain(
+ verboseDeprecationsNote()
+ );
+ });
+
+ it('omits the note about verboseDeprecations when verboseDeprecations is true', function() {
+ var deprecator = new jasmineUnderTest.Deprecator({});
+ var runnable = jasmine.createSpyObj('runnable', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+
+ deprecator.verboseDeprecations(true);
+ deprecator.addDeprecationWarning(runnable, 'the message');
+
+ expect(runnable.addDeprecationWarning).toHaveBeenCalledTimes(1);
+ expect(
+ runnable.addDeprecationWarning.calls.argsFor(0)[0].message
+ ).not.toContain(verboseDeprecationsNote());
+ expect(console.error).toHaveBeenCalledTimes(1);
+ expect(console.error.calls.argsFor(0)[0]).not.toContain(
+ verboseDeprecationsNote()
+ );
+ });
+
+ describe('When the deprecation is an Error', function() {
+ // This form is used by external systems like atom-jasmine3-test-runner
+ // to report their own deprecations through Jasmine. See
+ // .
+ it('passes the error through unchanged', function() {
+ var deprecator = new jasmineUnderTest.Deprecator({});
+ var runnable = jasmine.createSpyObj('runnable', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+ var exceptionFormatter = new jasmineUnderTest.ExceptionFormatter();
+ var deprecation, originalStack;
+
+ try {
+ throw new Error('the deprecation');
+ } catch (err) {
+ deprecation = err;
+ originalStack = err.stack;
+ }
+
+ deprecator.addDeprecationWarning(runnable, deprecation);
+
+ expect(runnable.addDeprecationWarning).toHaveBeenCalledTimes(1);
+ expect(
+ runnable.addDeprecationWarning.calls.argsFor(0)[0].message
+ ).toEqual('the deprecation');
+ expect(runnable.addDeprecationWarning.calls.argsFor(0)[0].stack).toBe(
+ originalStack
+ );
+ expect(console.error).toHaveBeenCalledTimes(1);
+ expect(console.error.calls.argsFor(0)[0].message).toEqual(
+ 'the deprecation'
+ );
+ expect(console.error.calls.argsFor(0)[0].stack).toEqual(originalStack);
+ });
+
+ it('reports the deprecation every time, regardless of config.verboseDeprecations', function() {
+ var deprecator = new jasmineUnderTest.Deprecator({});
+ var runnable = jasmine.createSpyObj('runnable', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+ var deprecation;
+
+ try {
+ throw new Error('the deprecation');
+ } catch (err) {
+ deprecation = err;
+ }
+
+ deprecator.addDeprecationWarning(runnable, deprecation);
+ deprecator.addDeprecationWarning(runnable, deprecation);
+
+ expect(runnable.addDeprecationWarning).toHaveBeenCalledTimes(2);
+ expect(console.error).toHaveBeenCalledTimes(2);
+ });
+
+ it('omits the note about verboseDeprecations', function() {
+ var deprecator = new jasmineUnderTest.Deprecator({});
+ var runnable = jasmine.createSpyObj('runnable', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+ var deprecation;
+
+ try {
+ throw new Error('the deprecation');
+ } catch (err) {
+ deprecation = err;
+ }
+
+ deprecator.addDeprecationWarning(runnable, deprecation);
+
+ expect(runnable.addDeprecationWarning).toHaveBeenCalledTimes(1);
+ expect(
+ runnable.addDeprecationWarning.calls.argsFor(0)[0].message
+ ).not.toContain(verboseDeprecationsNote());
+ expect(console.error).toHaveBeenCalledTimes(1);
+ expect(console.error.calls.argsFor(0)[0]).not.toContain(
+ verboseDeprecationsNote()
+ );
+ });
+ });
+
+ function verboseDeprecationsNote() {
+ return (
+ 'Note: This message will be shown only once. Set ' +
+ 'config.verboseDeprecations to true to see every occurrence.'
+ );
+ }
+
+ function testStackTrace(options) {
+ var deprecator = new jasmineUnderTest.Deprecator({});
+ var runnable = jasmine.createSpyObj('runnable', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+
+ deprecator.addDeprecationWarning(runnable, 'the message', options);
+
+ expect(runnable.addDeprecationWarning).toHaveBeenCalledWith({
+ message: jasmine.stringMatching(/^the message/),
+ omitStackTrace: false
+ });
+ expect(console.error).toHaveBeenCalledTimes(1);
+ expect(console.error.calls.argsFor(0)[0]).toContain('the message');
+ expect(console.error.calls.argsFor(0)[0]).toContain('DeprecatorSpec.js');
+ }
+
+ function testNoStackTrace(options) {
+ var deprecator = new jasmineUnderTest.Deprecator({});
+ var runnable = jasmine.createSpyObj('runnable', [
+ 'addDeprecationWarning',
+ 'getFullName'
+ ]);
+
+ deprecator.addDeprecationWarning(runnable, 'the message', options);
+
+ expect(runnable.addDeprecationWarning).toHaveBeenCalledWith({
+ message: jasmine.stringMatching(/^the message/),
+ omitStackTrace: true
+ });
+ }
+ });
+});
diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js
index 23c0d373..56412c7a 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.'
+ );
});
});
@@ -289,60 +421,6 @@ describe('Env', function() {
});
});
- describe('#deprecatedOnceWithStack', function() {
- it('includes a stack trace', function() {
- spyOn(env, 'deprecated');
-
- env.deprecatedOnceWithStack('msg');
-
- expect(env.deprecated).toHaveBeenCalled();
- var msg = env.deprecated.calls.argsFor(0)[0];
- expect(msg).toContain('msg');
- expect(msg).toContain('EnvSpec.js');
- expect(msg).not.toContain('Error');
- });
-
- describe('When verboseDeprecations is true', function() {
- it('calls #deprecated every time', function() {
- env.configure({ verboseDeprecations: true });
- spyOn(env, 'deprecated');
-
- env.deprecatedOnceWithStack('msg');
- env.deprecatedOnceWithStack('msg');
- expect(env.deprecated).toHaveBeenCalledWith(
- jasmine.stringMatching(/msg/)
- );
- expect(env.deprecated).toHaveBeenCalledTimes(2);
- expect(env.deprecated).not.toHaveBeenCalledWith(
- jasmine.stringMatching(/only once/)
- );
- });
- });
-
- describe('When verboseDeprecations is false', function() {
- it('calls #deprecated once per unique message', function() {
- env.configure({ verboseDeprecations: false });
- spyOn(env, 'deprecated');
-
- env.deprecatedOnceWithStack('foo');
- env.deprecatedOnceWithStack('bar');
- env.deprecatedOnceWithStack('foo');
-
- expect(env.deprecated).toHaveBeenCalledWith(
- jasmine.stringMatching(
- /foo\nNote: This message will be shown only once. Set config.verboseDeprecations to true to see every occurrence/m
- )
- );
- expect(env.deprecated).toHaveBeenCalledWith(
- jasmine.stringMatching(
- /bar\nNote: This message will be shown only once. Set config.verboseDeprecations to true to see every occurrence/m
- )
- );
- expect(env.deprecated).toHaveBeenCalledTimes(2);
- });
- });
- });
-
describe('when not constructed with suppressLoadErrors: true', function() {
it('installs a global error handler on construction', function() {
var globalErrors = jasmine.createSpyObj('globalErrors', [
diff --git a/spec/core/integration/DeprecationSpec.js b/spec/core/integration/DeprecationSpec.js
new file mode 100644
index 00000000..5f9fb454
--- /dev/null
+++ b/spec/core/integration/DeprecationSpec.js
@@ -0,0 +1,322 @@
+/* eslint no-console: 0 */
+describe('Deprecation (integration)', function() {
+ var env;
+
+ beforeEach(function() {
+ env = new jasmineUnderTest.Env();
+ });
+
+ afterEach(function() {
+ env.cleanup_();
+ });
+
+ it('reports a deprecation on the top suite', function(done) {
+ var reporter = jasmine.createSpyObj('reporter', ['jasmineDone']);
+ env.addReporter(reporter);
+ spyOn(console, 'error');
+
+ env.beforeAll(function() {
+ env.deprecated('the message');
+ });
+ env.it('a spec', function() {});
+
+ env.execute(null, function() {
+ expect(reporter.jasmineDone).toHaveBeenCalledWith(
+ jasmine.objectContaining({
+ deprecationWarnings: [
+ jasmine.objectContaining({
+ message: jasmine.stringMatching(/^the message/)
+ })
+ ]
+ })
+ );
+ expect(console.error).toHaveBeenCalledWith(
+ jasmine.stringMatching(/^DEPRECATION: the message/)
+ );
+ done();
+ });
+ });
+
+ it('reports a deprecation on a descendent suite', function(done) {
+ var reporter = jasmine.createSpyObj('reporter', ['suiteDone']);
+ env.addReporter(reporter);
+ spyOn(console, 'error');
+
+ env.describe('a suite', function() {
+ env.beforeAll(function() {
+ env.deprecated('the message');
+ });
+ env.it('a spec', function() {});
+ });
+
+ env.execute(null, function() {
+ expect(reporter.suiteDone).toHaveBeenCalledWith(
+ jasmine.objectContaining({
+ deprecationWarnings: [
+ jasmine.objectContaining({
+ message: jasmine.stringMatching(/^the message/)
+ })
+ ]
+ })
+ );
+ expect(console.error).toHaveBeenCalledWith(
+ jasmine.stringMatching(
+ /^DEPRECATION: the message \(in suite: a suite\)/
+ )
+ );
+ done();
+ });
+ });
+
+ it('reports a deprecation on a spec', function(done) {
+ var reporter = jasmine.createSpyObj('reporter', ['specDone']);
+ env.addReporter(reporter);
+ spyOn(console, 'error');
+
+ env.describe('a suite', function() {
+ env.it('a spec', function() {
+ env.deprecated('the message');
+ });
+ });
+
+ env.execute(null, function() {
+ expect(reporter.specDone).toHaveBeenCalledWith(
+ jasmine.objectContaining({
+ deprecationWarnings: [
+ jasmine.objectContaining({
+ message: jasmine.stringMatching(/^the message/)
+ })
+ ]
+ })
+ );
+ expect(console.error).toHaveBeenCalledWith(
+ jasmine.stringMatching(
+ /^DEPRECATION: the message \(in spec: a suite a spec\)/
+ )
+ );
+ done();
+ });
+ });
+
+ it('omits the suite or spec context when ignoreRunnable is true', function(done) {
+ var reporter = jasmine.createSpyObj('reporter', ['jasmineDone']);
+ env.addReporter(reporter);
+ spyOn(console, 'error');
+
+ env.it('a spec', function() {
+ env.deprecated('the message', { ignoreRunnable: true });
+ });
+
+ env.execute(null, function() {
+ expect(reporter.jasmineDone).toHaveBeenCalledWith(
+ jasmine.objectContaining({
+ deprecationWarnings: [
+ jasmine.objectContaining({
+ message: jasmine.stringMatching(/^the message/)
+ })
+ ]
+ })
+ );
+ expect(console.error).toHaveBeenCalledWith(
+ jasmine.stringMatching(/the message/)
+ );
+ expect(console.error).not.toHaveBeenCalledWith(
+ jasmine.stringMatching(/a spec/)
+ );
+ done();
+ });
+ });
+
+ it('includes the stack trace', function(done) {
+ var reporter = jasmine.createSpyObj('reporter', ['specDone']);
+ env.addReporter(reporter);
+ spyOn(console, 'error');
+
+ env.describe('a suite', function() {
+ env.it('a spec', function() {
+ env.deprecated('the message');
+ });
+ });
+
+ env.execute(null, function() {
+ expect(reporter.specDone).toHaveBeenCalledWith(
+ jasmine.objectContaining({
+ deprecationWarnings: [
+ jasmine.objectContaining({
+ stack: jasmine.stringMatching(/DeprecationSpec.js/)
+ })
+ ]
+ })
+ );
+ expect(console.error).toHaveBeenCalled();
+ expect(console.error.calls.argsFor(0)[0].replace(/\n/g, 'NL')).toMatch(
+ /^DEPRECATION: the message \(in spec: a suite a spec\)NL.*DeprecationSpec.js/
+ );
+ done();
+ });
+ });
+
+ it('excludes the stack trace when omitStackTrace is true', function(done) {
+ var reporter = jasmine.createSpyObj('reporter', ['specDone']);
+ env.addReporter(reporter);
+ spyOn(console, 'error');
+
+ env.describe('a suite', function() {
+ env.it('a spec', function() {
+ env.deprecated('the message', { omitStackTrace: true });
+ });
+ });
+
+ env.execute(null, function() {
+ expect(reporter.specDone).toHaveBeenCalledWith(
+ jasmine.objectContaining({
+ deprecationWarnings: [
+ jasmine.objectContaining({
+ stack: jasmine.falsy()
+ })
+ ]
+ })
+ );
+ expect(console.error).toHaveBeenCalled();
+ expect(console.error).not.toHaveBeenCalledWith(
+ jasmine.stringMatching(/DeprecationSpec.js/)
+ );
+ done();
+ });
+ });
+
+ it('emits a given deprecation only once', function(done) {
+ var reporter = jasmine.createSpyObj('reporter', ['specDone', 'suiteDone']);
+ env.addReporter(reporter);
+ spyOn(console, 'error');
+
+ env.describe('a suite', function() {
+ env.beforeAll(function() {
+ env.deprecated('the message');
+ env.deprecated('the message');
+ });
+
+ env.it('a spec', function() {
+ env.deprecated('the message');
+ env.deprecated('a different message');
+ });
+ });
+
+ env.execute(null, function() {
+ expect(reporter.suiteDone).toHaveBeenCalledWith(
+ jasmine.objectContaining({
+ deprecationWarnings: [
+ // only one
+ jasmine.objectContaining({
+ message: jasmine.stringMatching(/^the message/)
+ })
+ ]
+ })
+ );
+ expect(reporter.specDone).toHaveBeenCalledWith(
+ jasmine.objectContaining({
+ deprecationWarnings: [
+ // only the other one
+ jasmine.objectContaining({
+ message: jasmine.stringMatching(/^a different message/)
+ })
+ ]
+ })
+ );
+ expect(console.error).toHaveBeenCalledTimes(2);
+ expect(console.error).toHaveBeenCalledWith(
+ jasmine.stringMatching(
+ /^DEPRECATION: the message \(in suite: a suite\)/
+ )
+ );
+ expect(console.error).toHaveBeenCalledWith(
+ jasmine.stringMatching(
+ /^DEPRECATION: a different message \(in spec: a suite a spec\)/
+ )
+ );
+ done();
+ });
+ });
+
+ it('emits a given deprecation each time when config.verboseDeprecations is true', function(done) {
+ var reporter = jasmine.createSpyObj('reporter', ['specDone', 'suiteDone']);
+ env.addReporter(reporter);
+ spyOn(console, 'error');
+
+ env.configure({ verboseDeprecations: true });
+
+ env.describe('a suite', function() {
+ env.beforeAll(function() {
+ env.deprecated('the message');
+ env.deprecated('the message');
+ });
+
+ env.it('a spec', function() {
+ env.deprecated('the message');
+ });
+ });
+
+ env.execute(null, function() {
+ expect(reporter.suiteDone).toHaveBeenCalledWith(
+ jasmine.objectContaining({
+ deprecationWarnings: [
+ jasmine.objectContaining({
+ message: jasmine.stringMatching(/^the message/)
+ }),
+ jasmine.objectContaining({
+ message: jasmine.stringMatching(/^the message/)
+ })
+ ]
+ })
+ );
+ expect(reporter.specDone).toHaveBeenCalledWith(
+ jasmine.objectContaining({
+ deprecationWarnings: [
+ jasmine.objectContaining({
+ message: jasmine.stringMatching(/^the message/)
+ })
+ ]
+ })
+ );
+ expect(console.error).toHaveBeenCalledTimes(3);
+ expect(console.error.calls.argsFor(0)[0]).toMatch(
+ /^DEPRECATION: the message \(in suite: a suite\)/
+ );
+ expect(console.error.calls.argsFor(1)[0]).toMatch(
+ /^DEPRECATION: the message \(in suite: a suite\)/
+ );
+ expect(console.error.calls.argsFor(2)[0]).toMatch(
+ /^DEPRECATION: the message \(in spec: a suite a spec\)/
+ );
+ expect(console.error.calls.argsFor(2)[0]).toMatch(
+ /^DEPRECATION: the message \(in spec: a suite a spec\)/
+ );
+ done();
+ });
+ });
+
+ it('handles deprecations that occur before execute() is called', function(done) {
+ var reporter = jasmine.createSpyObj('reporter', ['jasmineDone']);
+ env.addReporter(reporter);
+ spyOn(console, 'error');
+
+ env.deprecated('the message');
+ env.it('a spec', function() {});
+
+ env.execute(null, function() {
+ expect(reporter.jasmineDone).toHaveBeenCalledWith(
+ jasmine.objectContaining({
+ deprecationWarnings: [
+ jasmine.objectContaining({
+ message: jasmine.stringMatching(/^the message/)
+ })
+ ]
+ })
+ );
+ expect(console.error).toHaveBeenCalledWith(
+ jasmine.stringMatching(/^DEPRECATION: the message/)
+ );
+ done();
+ });
+ });
+});
diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js
index d476426f..707bab02 100644
--- a/spec/core/integration/EnvSpec.js
+++ b/spec/core/integration/EnvSpec.js
@@ -2627,70 +2627,6 @@ describe('Env integration', function() {
});
});
- it('should report deprecation warnings on the correct specs and suites', function(done) {
- var reporter = jasmine.createSpyObj('reporter', [
- 'jasmineDone',
- 'suiteDone',
- 'specDone'
- ]);
-
- // prevent deprecation from being displayed, as well as letting us observe calls
- spyOn(console, 'error');
-
- env.addReporter(reporter);
-
- env.deprecated('top level deprecation');
-
- env.describe('suite', function() {
- env.beforeAll(function() {
- env.deprecated('suite level deprecation');
- });
-
- env.it('spec', function() {
- env.deprecated('spec level deprecation');
- });
- });
-
- env.execute(null, function() {
- var result = reporter.jasmineDone.calls.argsFor(0)[0];
- expect(result.deprecationWarnings).toEqual([
- jasmine.objectContaining({ message: 'top level deprecation' })
- ]);
- /* eslint-disable-next-line no-console */
- expect(console.error).toHaveBeenCalledWith(
- 'DEPRECATION: top level deprecation'
- );
-
- expect(reporter.suiteDone).toHaveBeenCalledWith(
- jasmine.objectContaining({
- fullName: 'suite',
- deprecationWarnings: [
- jasmine.objectContaining({ message: 'suite level deprecation' })
- ]
- })
- );
- /* eslint-disable-next-line no-console */
- expect(console.error).toHaveBeenCalledWith(
- 'DEPRECATION: suite level deprecation (in suite: suite)'
- );
-
- expect(reporter.specDone).toHaveBeenCalledWith(
- jasmine.objectContaining({
- fullName: 'suite spec',
- deprecationWarnings: [
- jasmine.objectContaining({ message: 'spec level deprecation' })
- ]
- })
- );
- /* eslint-disable-next-line no-console */
- expect(console.error).toHaveBeenCalledWith(
- 'DEPRECATION: spec level deprecation (in spec: suite spec)'
- );
-
- done();
- });
- });
-
it('should report deprecation stack with an error object', function(done) {
var exceptionFormatter = new jasmineUnderTest.ExceptionFormatter(),
reporter = jasmine.createSpyObj('reporter', [
diff --git a/spec/helpers/checkForProxy.js b/spec/helpers/checkForProxy.js
new file mode 100644
index 00000000..b4062d0b
--- /dev/null
+++ b/spec/helpers/checkForProxy.js
@@ -0,0 +1,17 @@
+/* eslint-disable compat/compat */
+(function(env) {
+ function hasProxyConstructor() {
+ try {
+ new Proxy({}, {});
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ env.requireProxy = function() {
+ if (!hasProxyConstructor()) {
+ env.pending('Environment does not support Proxy');
+ }
+ };
+})(jasmine.getEnv());
diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js
index 2db0e160..c21aa66e 100644
--- a/spec/html/HtmlReporterSpec.js
+++ b/spec/html/HtmlReporterSpec.js
@@ -303,6 +303,128 @@ describe('HtmlReporter', function() {
expect(alertBars[3].innerHTML).toMatch(/global deprecation/);
expect(alertBars[3].innerHTML).not.toMatch(/in /);
});
+
+ it('displays expandable stack traces', function() {
+ var container = document.createElement('div'),
+ getContainer = function() {
+ return container;
+ },
+ reporter = new jasmineUnderTest.HtmlReporter({
+ env: env,
+ getContainer: getContainer,
+ createElement: function() {
+ return document.createElement.apply(document, arguments);
+ },
+ createTextNode: function() {
+ return document.createTextNode.apply(document, arguments);
+ }
+ }),
+ expander,
+ expanderLink,
+ expanderContents;
+
+ reporter.initialize();
+
+ reporter.jasmineStarted({});
+ reporter.jasmineDone({
+ deprecationWarnings: [
+ {
+ message: 'a deprecation',
+ stack: 'a stack trace'
+ }
+ ],
+ failedExpectations: []
+ });
+
+ expander = container.querySelector(
+ '.jasmine-alert .jasmine-bar .jasmine-expander'
+ );
+ expanderContents = expander.querySelector('.jasmine-expander-contents');
+ expect(expanderContents.textContent).toMatch(/a stack trace/);
+
+ expanderLink = expander.querySelector('a');
+ expect(expander).not.toHaveClass('jasmine-expanded');
+ expect(expanderLink.textContent).toMatch(/Show stack trace/);
+
+ expanderLink.click();
+ expect(expander).toHaveClass('jasmine-expanded');
+ expect(expanderLink.textContent).toMatch(/Hide stack trace/);
+ expanderLink.click();
+
+ expect(expander).not.toHaveClass('jasmine-expanded');
+ expect(expanderLink.textContent).toMatch(/Show stack trace/);
+ });
+
+ it('omits the expander when there is no stack trace', function() {
+ var container = document.createElement('div'),
+ getContainer = function() {
+ return container;
+ },
+ reporter = new jasmineUnderTest.HtmlReporter({
+ env: env,
+ getContainer: getContainer,
+ createElement: function() {
+ return document.createElement.apply(document, arguments);
+ },
+ createTextNode: function() {
+ return document.createTextNode.apply(document, arguments);
+ }
+ }),
+ warningBar;
+
+ reporter.initialize();
+
+ reporter.jasmineStarted({});
+ reporter.jasmineDone({
+ deprecationWarnings: [
+ {
+ message: 'a deprecation',
+ stack: ''
+ }
+ ],
+ failedExpectations: []
+ });
+
+ warningBar = container.querySelector('.jasmine-warning');
+ expect(warningBar.querySelector('.jasmine-expander')).toBeFalsy();
+ });
+
+ it('nicely formats the verboseDeprecations note', function() {
+ var container = document.createElement('div'),
+ getContainer = function() {
+ return container;
+ },
+ reporter = new jasmineUnderTest.HtmlReporter({
+ env: env,
+ getContainer: getContainer,
+ createElement: function() {
+ return document.createElement.apply(document, arguments);
+ },
+ createTextNode: function() {
+ return document.createTextNode.apply(document, arguments);
+ }
+ }),
+ alertBar;
+
+ reporter.initialize();
+
+ reporter.jasmineStarted({});
+ reporter.jasmineDone({
+ deprecationWarnings: [
+ {
+ message:
+ 'a deprecation\nNote: This message will be shown only once. Set config.verboseDeprecations to true to see every occurrence.'
+ }
+ ],
+ failedExpectations: []
+ });
+
+ alertBar = container.querySelector('.jasmine-warning');
+
+ expect(alertBar.innerHTML).toMatch(
+ /a deprecation
Note: This message will be shown only once/
+ );
+ });
});
describe('when Jasmine is done', function() {
diff --git a/spec/support/jasmine-browser.js b/spec/support/jasmine-browser.js
index 9062bf8a..1fc16d01 100644
--- a/spec/support/jasmine-browser.js
+++ b/spec/support/jasmine-browser.js
@@ -21,6 +21,7 @@ module.exports = {
'helpers/generator.js',
'helpers/BrowserFlags.js',
'helpers/checkForMap.js',
+ 'helpers/checkForProxy.js',
'helpers/checkForSet.js',
'helpers/checkForSymbol.js',
'helpers/checkForUrl.js',
diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json
index 1a0ca3b2..d6369bf2 100644
--- a/spec/support/jasmine.json
+++ b/spec/support/jasmine.json
@@ -8,6 +8,7 @@
"helpers/asyncAwait.js",
"helpers/generator.js",
"helpers/checkForMap.js",
+ "helpers/checkForProxy.js",
"helpers/checkForSet.js",
"helpers/checkForSymbol.js",
"helpers/checkForUrl.js",
diff --git a/src/core/Deprecator.js b/src/core/Deprecator.js
new file mode 100644
index 00000000..3e63c9d3
--- /dev/null
+++ b/src/core/Deprecator.js
@@ -0,0 +1,90 @@
+getJasmineRequireObj().Deprecator = function(j$) {
+ function Deprecator(topSuite) {
+ this.topSuite_ = topSuite;
+ this.verbose_ = false;
+ this.toSuppress_ = [];
+ }
+
+ var verboseNote =
+ 'Note: This message will be shown only once. ' +
+ 'Set config.verboseDeprecations to true to see every occurrence.';
+
+ Deprecator.prototype.verboseDeprecations = function(enabled) {
+ this.verbose_ = enabled;
+ };
+
+ // runnable is a spec or a suite.
+ // deprecation is a string or an Error.
+ // See Env#deprecated for a description of the options argument.
+ Deprecator.prototype.addDeprecationWarning = function(
+ runnable,
+ deprecation,
+ options
+ ) {
+ options = options || {};
+
+ if (!this.verbose_ && !j$.isError_(deprecation)) {
+ if (this.toSuppress_.indexOf(deprecation) !== -1) {
+ return;
+ }
+ this.toSuppress_.push(deprecation);
+ }
+
+ this.log_(runnable, deprecation, options);
+ this.report_(runnable, deprecation, options);
+ };
+
+ Deprecator.prototype.log_ = function(runnable, deprecation, options) {
+ var context;
+
+ if (j$.isError_(deprecation)) {
+ console.error(deprecation);
+ return;
+ }
+
+ if (runnable === this.topSuite_ || options.ignoreRunnable) {
+ context = '';
+ } else if (runnable.children) {
+ context = ' (in suite: ' + runnable.getFullName() + ')';
+ } else {
+ context = ' (in spec: ' + runnable.getFullName() + ')';
+ }
+
+ if (!options.omitStackTrace) {
+ context += '\n' + this.stackTrace_();
+ }
+
+ if (!this.verbose_) {
+ context += '\n' + verboseNote;
+ }
+
+ console.error('DEPRECATION: ' + deprecation + context);
+ };
+
+ Deprecator.prototype.stackTrace_ = function() {
+ var formatter = new j$.ExceptionFormatter();
+ return formatter.stack(j$.util.errorWithStack()).replace(/^Error\n/m, '');
+ };
+
+ Deprecator.prototype.report_ = function(runnable, deprecation, options) {
+ if (options.ignoreRunnable) {
+ runnable = this.topSuite_;
+ }
+
+ if (j$.isError_(deprecation)) {
+ runnable.addDeprecationWarning(deprecation);
+ return;
+ }
+
+ if (!this.verbose_) {
+ deprecation += '\n' + verboseNote;
+ }
+
+ runnable.addDeprecationWarning({
+ message: deprecation,
+ omitStackTrace: options.omitStackTrace || false
+ });
+ };
+
+ return Deprecator;
+};
diff --git a/src/core/Env.js b/src/core/Env.js
index 39c8cf81..3368de22 100644
--- a/src/core/Env.js
+++ b/src/core/Env.js
@@ -32,7 +32,6 @@ getJasmineRequireObj().Env = function(j$) {
var currentlyExecutingSuites = [];
var currentDeclarationSuite = null;
var hasFailures = false;
- var deprecationsToSuppress = [];
/**
* This represents the available options to configure Jasmine.
@@ -228,6 +227,7 @@ getJasmineRequireObj().Env = function(j$) {
if (configuration.hasOwnProperty('verboseDeprecations')) {
config.verboseDeprecations = configuration.verboseDeprecations;
+ deprecator.verboseDeprecations(config.verboseDeprecations);
}
};
@@ -482,48 +482,31 @@ getJasmineRequireObj().Env = function(j$) {
return buildExpectationResult(attrs);
};
- this.deprecated = function(deprecation) {
+ /**
+ * Causes a deprecation warning to be logged to the console and reported to
+ * reporters.
+ *
+ * The optional second parameter is an object that can have either of the
+ * following properties:
+ *
+ * omitStackTrace: Whether to omit the stack trace. Optional. Defaults to
+ * false. This option is ignored if the deprecation is an Error. Set this
+ * when the stack trace will not contain anything that helps the user find
+ * the source of the deprecation.
+ *
+ * ignoreRunnable: Whether to log the deprecation on the root suite, ignoring
+ * the spec or suite that's running when it happens. Optional. Defaults to
+ * false.
+ *
+ * @name Env#deprecated
+ * @since 2.99
+ * @function
+ * @param {String|Error} deprecation The deprecation message
+ * @param {Object} [options] Optional extra options, as described above
+ */
+ this.deprecated = function(deprecation, options) {
var runnable = currentRunnable() || topSuite;
- var context;
-
- if (runnable === topSuite) {
- context = '';
- } else if (runnable === currentSuite()) {
- context = ' (in suite: ' + runnable.getFullName() + ')';
- } else {
- context = ' (in spec: ' + runnable.getFullName() + ')';
- }
-
- runnable.addDeprecationWarning(deprecation);
- if (
- typeof console !== 'undefined' &&
- typeof console.error === 'function'
- ) {
- console.error('DEPRECATION: ' + deprecation + context);
- }
- };
-
- this.deprecatedOnceWithStack = function(deprecation) {
- var formatter = new j$.ExceptionFormatter(),
- stackTrace = formatter
- .stack(j$.util.errorWithStack())
- .replace(/^Error\n/m, '');
-
- if (config.verboseDeprecations) {
- this.deprecated(deprecation + '\n' + stackTrace);
- } else {
- if (deprecationsToSuppress.indexOf(deprecation) === -1) {
- this.deprecated(
- deprecation +
- '\n' +
- 'Note: This message will be shown only once. ' +
- 'Set config.verboseDeprecations to true to see every occurrence.\n' +
- stackTrace
- );
- }
-
- deprecationsToSuppress.push(deprecation);
- }
+ deprecator.addDeprecationWarning(runnable, deprecation, options);
};
var queueRunnerFactory = function(options, args) {
@@ -559,6 +542,7 @@ getJasmineRequireObj().Env = function(j$) {
asyncExpectationFactory: suiteAsyncExpectationFactory,
expectationResultFactory: expectationResultFactory
});
+ var deprecator = new j$.Deprecator(topSuite);
defaultResourcesForRunnable(topSuite.id);
currentDeclarationSuite = topSuite;
@@ -570,7 +554,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/ExpectationResult.js b/src/core/ExpectationResult.js
index 7f298bc7..d3ea5cb3 100644
--- a/src/core/ExpectationResult.js
+++ b/src/core/ExpectationResult.js
@@ -16,7 +16,7 @@ getJasmineRequireObj().buildExpectationResult = function(j$) {
var result = {
matcherName: options.matcherName,
message: message(),
- stack: stack(),
+ stack: options.omitStackTrace ? '' : stack(),
passed: options.passed
};
diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js
index b3458bab..e811f0b8 100644
--- a/src/core/QueueRunner.js
+++ b/src/core/QueueRunner.js
@@ -215,8 +215,12 @@ getJasmineRequireObj().QueueRunner = function(j$) {
};
QueueRunner.prototype.diagnoseConflictingAsync_ = function(fn, retval) {
+ var msg;
+
if (retval && j$.isFunction_(retval.then)) {
- // Issue a warning that matches the user's code
+ // Issue a warning that matches the user's code.
+ // Omit the stack trace because there's almost certainly no user code
+ // on the stack at this point.
if (j$.isAsyncFunction_(fn)) {
this.onException(
'An asynchronous before/it/after ' +
@@ -232,6 +236,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
'function to not return a promise.'
);
}
+
+ this.deprecated(msg, { omitStackTrace: true });
}
};
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 aac94b87..2dd88c06 100644
--- a/src/core/requireCore.js
+++ b/src/core/requireCore.js
@@ -42,7 +42,10 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
j$.getClearStack = jRequire.clearStack(j$);
j$.Clock = jRequire.Clock();
j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$);
+ j$.Deprecator = jRequire.Deprecator(j$);
j$.Env = jRequire.Env(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();
diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js
index e591ccac..98f958d1 100644
--- a/src/html/HtmlReporter.js
+++ b/src/html/HtmlReporter.js
@@ -277,7 +277,8 @@ jasmineRequire.HtmlReporter = function(j$) {
addDeprecationWarnings(doneResult);
for (i = 0; i < deprecationWarnings.length; i++) {
- var context;
+ var children = [],
+ context;
switch (deprecationWarnings[i].runnableType) {
case 'spec':
@@ -290,13 +291,23 @@ jasmineRequire.HtmlReporter = function(j$) {
context = '';
}
+ deprecationWarnings[i].message.split('\n').forEach(function(line) {
+ children.push(line);
+ children.push(createDom('br'));
+ });
+
+ children[0] = 'DEPRECATION: ' + children[0];
+ children.push(context);
+
+ if (deprecationWarnings[i].stack) {
+ children.push(createExpander(deprecationWarnings[i].stack));
+ }
+
alert.appendChild(
createDom(
'span',
{ className: 'jasmine-bar jasmine-warning' },
- 'DEPRECATION: ' + deprecationWarnings[i].message,
- createDom('br'),
- context
+ children
)
);
}
@@ -611,17 +622,44 @@ jasmineRequire.HtmlReporter = function(j$) {
if (result && result.deprecationWarnings) {
for (var i = 0; i < result.deprecationWarnings.length; i++) {
var warning = result.deprecationWarnings[i].message;
- if (!j$.util.arrayContains(warning)) {
- deprecationWarnings.push({
- message: warning,
- runnableName: result.fullName,
- runnableType: runnableType
- });
- }
+ deprecationWarnings.push({
+ message: warning,
+ stack: result.deprecationWarnings[i].stack,
+ runnableName: result.fullName,
+ runnableType: runnableType
+ });
}
}
}
+ function createExpander(stackTrace) {
+ var expandLink = createDom('a', { href: '#' }, 'Show stack trace');
+ var root = createDom(
+ 'div',
+ { className: 'jasmine-expander' },
+ expandLink,
+ createDom(
+ 'div',
+ { className: 'jasmine-expander-contents jasmine-stack-trace' },
+ stackTrace
+ )
+ );
+
+ expandLink.addEventListener('click', function(e) {
+ e.preventDefault();
+
+ if (root.classList.contains('jasmine-expanded')) {
+ root.classList.remove('jasmine-expanded');
+ expandLink.textContent = 'Show stack trace';
+ } else {
+ root.classList.add('jasmine-expanded');
+ expandLink.textContent = 'Hide stack trace';
+ }
+ });
+
+ return root;
+ }
+
function find(selector) {
return getContainer().querySelector('.jasmine_html-reporter ' + selector);
}
@@ -635,11 +673,23 @@ jasmineRequire.HtmlReporter = function(j$) {
}
}
- function createDom(type, attrs, childrenVarArgs) {
- var el = createElement(type);
+ function createDom(type, attrs, childrenArrayOrVarArgs) {
+ var el = createElement(type),
+ children,
+ i;
- for (var i = 2; i < arguments.length; i++) {
- var child = arguments[i];
+ if (j$.isArray_(childrenArrayOrVarArgs)) {
+ children = childrenArrayOrVarArgs;
+ } else {
+ children = [];
+
+ for (i = 2; i < arguments.length; i++) {
+ children.push(arguments[i]);
+ }
+ }
+
+ for (i = 0; i < children.length; i++) {
+ var child = children[i];
if (typeof child === 'string') {
el.appendChild(createTextNode(child));
diff --git a/src/html/_HTMLReporter.scss b/src/html/_HTMLReporter.scss
index 2b128a04..d2d58b0d 100644
--- a/src/html/_HTMLReporter.scss
+++ b/src/html/_HTMLReporter.scss
@@ -231,6 +231,8 @@ body {
}
&.jasmine-warning {
+ margin-top: $margin-unit;
+ margin-bottom: $margin-unit;
background-color: $pending-color;
color: $text-color;
}
@@ -386,4 +388,27 @@ body {
background: white;
white-space: pre;
}
+
+ .jasmine-expander {
+ a {
+ display: block;
+ margin-left: $margin-unit;
+ color: blue;
+ text-decoration: underline;
+ }
+ }
+
+ .jasmine-expander-contents {
+ display: none;
+ }
+
+ .jasmine-expanded {
+ padding-bottom: 10px;
+ }
+
+ .jasmine-expanded .jasmine-expander-contents {
+ display: block;
+ margin-left: $margin-unit;
+ padding: 5px;
+ }
}