Added support for custom object formatters
Custom object formatters allow users to customize how an object is
stringified in matcher failure messages. This can already be done by
adding a `jasmineToString` method to the objects in question. But
it's not always desirable or possible to do that, particularly when
objects of a given "type" do not inherit from a specific prototype.
For instance, suppose a web service returns a list of foos that are
deserialized from JSON, e.g.:
{ fooId: 42, /* more properties */ }
The only way to define `jasmineToString` on those is by writing code to
add it to each instance at runtime. But a custom object formatter can
recognize that the object it's looking at is a foo and format it
accordingly:
jasmine.addCustomObjectFormatter(function(obj) {
if (typeof obj.fooId !== 'number') {
return undefined;
}
return '[Foo with ID ' + obj.fooId + ']';
});
Unlike `jasmineToString`, custom object formatters are scoped to a
particular spec or suite and don't require any changes to the code
under test.
This commit is contained in:
committed by
Steve Gravrock
parent
1f23f1e4d2
commit
25816a6e77
@@ -296,6 +296,18 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
this.addCustomObjectFormatter = function(formatter) {
|
||||
if (!currentRunnable()) {
|
||||
throw new Error(
|
||||
'Custom object formatters must be added in a before function or a spec'
|
||||
);
|
||||
}
|
||||
|
||||
runnableResources[currentRunnable().id].customObjectFormatters.push(
|
||||
formatter
|
||||
);
|
||||
};
|
||||
|
||||
j$.Expectation.addCoreMatchers(j$.matchers);
|
||||
j$.Expectation.addAsyncCoreMatchers(j$.asyncMatchers);
|
||||
|
||||
@@ -310,7 +322,9 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
};
|
||||
|
||||
var makePrettyPrinter = function() {
|
||||
return j$.makePrettyPrinter();
|
||||
var customObjectFormatters =
|
||||
runnableResources[currentRunnable().id].customObjectFormatters;
|
||||
return j$.makePrettyPrinter(customObjectFormatters);
|
||||
};
|
||||
|
||||
var makeMatchersUtil = function() {
|
||||
@@ -360,7 +374,8 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
customMatchers: {},
|
||||
customAsyncMatchers: {},
|
||||
customSpyStrategies: {},
|
||||
defaultStrategyFn: undefined
|
||||
defaultStrategyFn: undefined,
|
||||
customObjectFormatters: []
|
||||
};
|
||||
|
||||
if (runnableResources[parentRunnableId]) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
getJasmineRequireObj().makePrettyPrinter = function(j$) {
|
||||
function SinglePrettyPrintRun() {
|
||||
function SinglePrettyPrintRun(customObjectFormatters, pp) {
|
||||
this.customObjectFormatters_ = customObjectFormatters;
|
||||
this.ppNestLevel_ = 0;
|
||||
this.seen = [];
|
||||
this.length = 0;
|
||||
this.stringParts = [];
|
||||
this.pp_ = pp;
|
||||
}
|
||||
|
||||
function hasCustomToString(value) {
|
||||
@@ -24,7 +26,11 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
|
||||
SinglePrettyPrintRun.prototype.format = function(value) {
|
||||
this.ppNestLevel_++;
|
||||
try {
|
||||
if (j$.util.isUndefined(value)) {
|
||||
var customFormatResult = this.applyCustomFormatters_(value);
|
||||
|
||||
if (customFormatResult) {
|
||||
this.emitScalar(customFormatResult);
|
||||
} else if (j$.util.isUndefined(value)) {
|
||||
this.emitScalar('undefined');
|
||||
} else if (value === null) {
|
||||
this.emitScalar('null');
|
||||
@@ -33,7 +39,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
|
||||
} else if (value === j$.getGlobal()) {
|
||||
this.emitScalar('<global>');
|
||||
} else if (value.jasmineToString) {
|
||||
this.emitScalar(value.jasmineToString());
|
||||
this.emitScalar(value.jasmineToString(this.pp_));
|
||||
} else if (typeof value === 'string') {
|
||||
this.emitString(value);
|
||||
} else if (j$.isSpy(value)) {
|
||||
@@ -95,6 +101,18 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
SinglePrettyPrintRun.prototype.applyCustomFormatters_ = function(value) {
|
||||
var i, result;
|
||||
|
||||
for (i = 0; i < this.customObjectFormatters_.length; i++) {
|
||||
result = this.customObjectFormatters_[i](value);
|
||||
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SinglePrettyPrintRun.prototype.iterateObject = function(obj, fn) {
|
||||
var objKeys = keys(obj, j$.isArray_(obj));
|
||||
var isGetter = function isGetter(prop) {};
|
||||
@@ -365,11 +383,16 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
|
||||
return extraKeys;
|
||||
}
|
||||
|
||||
return function() {
|
||||
return function(value) {
|
||||
var prettyPrinter = new SinglePrettyPrintRun();
|
||||
return function(customObjectFormatters) {
|
||||
var pp = function(value) {
|
||||
var prettyPrinter = new SinglePrettyPrintRun(
|
||||
customObjectFormatters || [],
|
||||
pp
|
||||
);
|
||||
prettyPrinter.format(value);
|
||||
return prettyPrinter.stringParts.join('');
|
||||
};
|
||||
|
||||
return pp;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,8 +25,8 @@ getJasmineRequireObj().ArrayContaining = function(j$) {
|
||||
return true;
|
||||
};
|
||||
|
||||
ArrayContaining.prototype.jasmineToString = function () {
|
||||
return '<jasmine.arrayContaining(' + j$.pp(this.sample) +')>';
|
||||
ArrayContaining.prototype.jasmineToString = function (pp) {
|
||||
return '<jasmine.arrayContaining(' + pp(this.sample) +')>';
|
||||
};
|
||||
|
||||
return ArrayContaining;
|
||||
|
||||
@@ -23,8 +23,8 @@ getJasmineRequireObj().ArrayWithExactContents = function(j$) {
|
||||
return true;
|
||||
};
|
||||
|
||||
ArrayWithExactContents.prototype.jasmineToString = function() {
|
||||
return '<jasmine.arrayWithExactContents ' + j$.pp(this.sample) + '>';
|
||||
ArrayWithExactContents.prototype.jasmineToString = function(pp) {
|
||||
return '<jasmine.arrayWithExactContents(' + pp(this.sample) + ')>';
|
||||
};
|
||||
|
||||
return ArrayWithExactContents;
|
||||
|
||||
@@ -33,8 +33,8 @@ getJasmineRequireObj().MapContaining = function(j$) {
|
||||
return hasAllMatches;
|
||||
};
|
||||
|
||||
MapContaining.prototype.jasmineToString = function() {
|
||||
return '<jasmine.mapContaining(' + j$.pp(this.sample) + ')>';
|
||||
MapContaining.prototype.jasmineToString = function(pp) {
|
||||
return '<jasmine.mapContaining(' + pp(this.sample) + ')>';
|
||||
};
|
||||
|
||||
return MapContaining;
|
||||
|
||||
@@ -41,8 +41,8 @@ getJasmineRequireObj().ObjectContaining = function(j$) {
|
||||
return true;
|
||||
};
|
||||
|
||||
ObjectContaining.prototype.jasmineToString = function() {
|
||||
return '<jasmine.objectContaining(' + j$.pp(this.sample) + ')>';
|
||||
ObjectContaining.prototype.jasmineToString = function(pp) {
|
||||
return '<jasmine.objectContaining(' + pp(this.sample) + ')>';
|
||||
};
|
||||
|
||||
return ObjectContaining;
|
||||
|
||||
@@ -31,8 +31,8 @@ getJasmineRequireObj().SetContaining = function(j$) {
|
||||
return hasAllMatches;
|
||||
};
|
||||
|
||||
SetContaining.prototype.jasmineToString = function() {
|
||||
return '<jasmine.setContaining(' + j$.pp(this.sample) + ')>';
|
||||
SetContaining.prototype.jasmineToString = function(pp) {
|
||||
return '<jasmine.setContaining(' + pp(this.sample) + ')>';
|
||||
};
|
||||
|
||||
return SetContaining;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
getJasmineRequireObj().DiffBuilder = function(j$) {
|
||||
return function DiffBuilder() {
|
||||
return function DiffBuilder(config) {
|
||||
var path = new j$.ObjectPath(),
|
||||
mismatches = [];
|
||||
mismatches = [],
|
||||
prettyPrinter = (config || {}).prettyPrinter || j$.makePrettyPrinter();
|
||||
|
||||
return {
|
||||
record: function (actual, expected, formatter) {
|
||||
formatter = formatter || defaultFormatter;
|
||||
mismatches.push(formatter(actual, expected, path));
|
||||
mismatches.push(formatter(actual, expected, path, prettyPrinter));
|
||||
},
|
||||
|
||||
getMessage: function () {
|
||||
@@ -21,12 +22,12 @@ getJasmineRequireObj().DiffBuilder = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
function defaultFormatter (actual, expected, path) {
|
||||
function defaultFormatter (actual, expected, path, prettyPrinter) {
|
||||
return 'Expected ' +
|
||||
path + (path.depth() ? ' = ' : '') +
|
||||
j$.pp(actual) +
|
||||
prettyPrinter(actual) +
|
||||
' to equal ' +
|
||||
j$.pp(expected) +
|
||||
prettyPrinter(expected) +
|
||||
'.';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
|
||||
if (i > 0) {
|
||||
message += ',';
|
||||
}
|
||||
message += ' ' + j$.pp(expected[i]);
|
||||
message += ' ' + self.pp(expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ getJasmineRequireObj().toEqual = function(j$) {
|
||||
var result = {
|
||||
pass: false
|
||||
},
|
||||
diffBuilder = j$.DiffBuilder();
|
||||
diffBuilder = j$.DiffBuilder({prettyPrinter: util.pp});
|
||||
|
||||
result.pass = util.equals(actual, expected, diffBuilder);
|
||||
|
||||
|
||||
@@ -316,6 +316,20 @@ getJasmineRequireObj().interface = function(jasmine, env) {
|
||||
return env.addAsyncMatchers(matchers);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a custom object formatter for the current scope of specs.
|
||||
*
|
||||
* _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}.
|
||||
* @name jasmine.addCustomObjectFormatter
|
||||
* @since 3.6.0
|
||||
* @function
|
||||
* @param {Function} formatter - A function which takes a value to format and returns a string if it knows how to format it, and `undefined` otherwise.
|
||||
* @see custom_object_formatter
|
||||
*/
|
||||
jasmine.addCustomObjectFormatter = function(formatter) {
|
||||
return env.addCustomObjectFormatter(formatter);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the currently booted mock {Clock} for this Jasmine environment.
|
||||
* @name jasmine.clock
|
||||
|
||||
Reference in New Issue
Block a user