Skip to content

Commit

Permalink
Merge 5ad5b6c into 7aa975e
Browse files Browse the repository at this point in the history
  • Loading branch information
sunesimonsen committed Apr 25, 2017
2 parents 7aa975e + 5ad5b6c commit f39e747
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 57 deletions.
1 change: 1 addition & 0 deletions documentation/api/promise-any.md
Expand Up @@ -59,6 +59,7 @@ return expect.promise.any({
output.error(aggregateError.message);
var errors = [];
for (var i = 0; i < aggregateError.length; i += 1) {
aggregateError[i].serializeMessage(expect.outputFormat())
errors.push(aggregateError[i]);
}
Expand Down
22 changes: 22 additions & 0 deletions documentation/assertions/function/when-called-with.md
Expand Up @@ -27,3 +27,25 @@ return expect(add, 'called with', [1, 2]).then(function (result) {
expect(result, 'to equal', 3);
});
```

When this assertion in used together with [to satisfy](/assertions/any/to-satisfy)
we make sure that `this` is bound correctly:

```js
function Greeter(prefix) {
this.prefix = prefix;
}

Greeter.prototype.greet = function (name) {
return this.prefix + name;
};

var helloGreeter = new Greeter('Hello, ')

expect(helloGreeter, 'to satisfy', {
greet: expect.it(
'when called with', ['John Doe'],
'to equal', 'Hello, John Doe'
)
});
```
17 changes: 17 additions & 0 deletions documentation/assertions/function/when-called.md
Expand Up @@ -27,3 +27,20 @@ return expect(giveMeFive, 'called').then(function (result) {
expect(result, 'to equal', 5);
});
```

When this assertion in used together with [to satisfy](/assertions/any/to-satisfy)
we make sure that `this` is bound correctly:

```js
function Person(name) {
this.name = name;
}

Person.prototype.toString = function () {
return this.name;
};

expect(new Person('John Doe'), 'to satisfy', {
toString: expect.it('when called to equal', 'John Doe')
});
```
67 changes: 39 additions & 28 deletions lib/Unexpected.js
Expand Up @@ -19,6 +19,11 @@ function isAssertionArg(arg) {
return arg.type.is('assertion');
}

function Context(unexpected) {
this.expect = unexpected;
this.level = 0;
}

var anyType = {
_unexpectedType: true,
name: 'any',
Expand Down Expand Up @@ -125,7 +130,7 @@ function getOrGroups(expectations) {
return orGroups;
}

function evaluateGroup(expect, subject, orGroup) {
function evaluateGroup(unexpected, context, subject, orGroup) {
return orGroup.map(function (expectation) {
var args = Array.prototype.slice.call(expectation);
args.unshift(subject);
Expand All @@ -140,14 +145,14 @@ function evaluateGroup(expect, subject, orGroup) {
return args[1](args[0]);
}
} else {
return expect.apply(expect, args);
return unexpected._expect.call(unexpected, context, args);
}
})
};
});
}

function writeGroupEvaluationsToOutput(expect, output, groupEvaluations) {
function writeGroupEvaluationsToOutput(output, groupEvaluations) {
var hasOrClauses = groupEvaluations.length > 1;
var hasAndClauses = groupEvaluations.some(function (groupEvaluation) {
return groupEvaluation.length > 1;
Expand Down Expand Up @@ -208,21 +213,27 @@ function writeGroupEvaluationsToOutput(expect, output, groupEvaluations) {
});
}

function createExpectIt(expect, expectations) {
function createExpectIt(unexpected, expectations) {
var orGroups = getOrGroups(expectations);

function expectIt(subject) {
function expectIt(subject, context) {
context = (
context &&
typeof context === 'object' &&
context instanceof Context
) ? context : new Context(unexpected);

var groupEvaluations = [];
var promises = [];
orGroups.forEach(function (orGroup) {
var evaluations = evaluateGroup(expect, subject, orGroup);
var evaluations = evaluateGroup(unexpected, context, subject, orGroup);
evaluations.forEach(function (evaluation) {
promises.push(evaluation.promise);
});
groupEvaluations.push(evaluations);
});

return oathbreaker(expect.promise.settle(promises).then(function () {
return oathbreaker(makePromise.settle(promises).then(function () {
groupEvaluations.forEach(function (groupEvaluation) {
groupEvaluation.forEach(function (evaluation) {
if (evaluation.promise.isRejected() && evaluation.promise.reason().errorMode === 'bubbleThrough') {
Expand All @@ -236,8 +247,8 @@ function createExpectIt(expect, expectations) {
return evaluation.promise.isFulfilled();
});
})) {
expect.fail(function (output) {
writeGroupEvaluationsToOutput(expect, output, groupEvaluations);
unexpected.fail(function (output) {
writeGroupEvaluationsToOutput(output, groupEvaluations);
});
}
}));
Expand All @@ -248,18 +259,18 @@ function createExpectIt(expect, expectations) {
expectIt.and = function () {
var copiedExpectations = expectations.slice();
copiedExpectations.push(arguments);
return createExpectIt(expect, copiedExpectations);
return createExpectIt(unexpected, copiedExpectations);
};
expectIt.or = function () {
var copiedExpectations = expectations.slice();
copiedExpectations.push(OR, arguments);
return createExpectIt(expect, copiedExpectations);
return createExpectIt(unexpected, copiedExpectations);
};
return expectIt;
}

Unexpected.prototype.it = function () { // ...
return createExpectIt(this.expect, [arguments]);
return createExpectIt(this, [arguments]);
};

Unexpected.prototype.equal = function (actual, expected, depth, seen) {
Expand Down Expand Up @@ -851,7 +862,7 @@ Unexpected.prototype.installPlugin = Unexpected.prototype.use; // Legacy alias

function installExpectMethods(unexpected) {
var expect = function () { /// ...
return unexpected._expect.apply(unexpected, arguments);
return unexpected._expect.call(unexpected, new Context(unexpected), arguments);
};
expect.it = unexpected.it.bind(unexpected);
expect.equal = unexpected.equal.bind(unexpected);
Expand Down Expand Up @@ -1104,9 +1115,12 @@ Unexpected.prototype.setErrorMessage = function (err) {
err.serializeMessage(this.outputFormat());
};

Unexpected.prototype._expect = function expect(subject, testDescriptionString) {
Unexpected.prototype._expect = function expect(context, args) {
var that = this;
if (arguments.length < 2) {
var subject = args[0];
var testDescriptionString = args[1];

if (args.length < 2) {
throw new Error('The expect function requires at least two parameters.');
} else if (testDescriptionString && testDescriptionString._expectIt) {
return that.expect.withError(function () {
Expand All @@ -1116,10 +1130,7 @@ Unexpected.prototype._expect = function expect(subject, testDescriptionString) {
});
}

var executionContext = {
expect: that,
serializeErrorsFromWrappedExpect: false
};
context.level++;

function executeExpect(subject, testDescriptionString, args) {
var assertionRule = that.lookupAssertionRule(subject, testDescriptionString, args);
Expand Down Expand Up @@ -1172,7 +1183,7 @@ Unexpected.prototype._expect = function expect(subject, testDescriptionString) {

utils.setPrototypeOfOrExtend(wrappedExpect, that._wrappedExpectProto);

wrappedExpect._context = executionContext;
wrappedExpect.context = context;
wrappedExpect.execute = wrappedExpect;
wrappedExpect.alternations = assertionRule.alternations;
wrappedExpect.flags = flags;
Expand Down Expand Up @@ -1205,34 +1216,34 @@ Unexpected.prototype._expect = function expect(subject, testDescriptionString) {
return oathbreaker(assertionRule.handler.apply(wrappedExpect, [wrappedExpect, subject].concat(args)));
}

var args = new Array(arguments.length - 2);
for (var i = 2 ; i < arguments.length ; i += 1) {
args[i - 2] = arguments[i];
}
try {
var result = executeExpect(subject, testDescriptionString, args);
var result = executeExpect(subject, testDescriptionString, Array.prototype.slice.call(args, 2));
if (isPendingPromise(result)) {
that.expect.notifyPendingPromise(result);
result = result.then(undefined, function (e) {
if (e && e._isUnexpected) {
context.level--;
if (e && e._isUnexpected && context.level === 0) {
that.setErrorMessage(e);
}
throw e;
});
} else {
executionContext.serializeErrorsFromWrappedExpect = true;
context.level--;
if (!result || typeof result.then !== 'function') {
result = makePromise.resolve(result);
}
}
return addAdditionalPromiseMethods(result, that.expect, subject);
} catch (e) {
context.level--;
if (e && e._isUnexpected) {
var newError = e;
if (typeof mochaPhantomJS !== 'undefined') {
newError = e.clone();
}
that.setErrorMessage(newError);
if (context.level === 0) {
that.setErrorMessage(newError);
}
throw newError;
}
throw e;
Expand Down
30 changes: 24 additions & 6 deletions lib/assertions.js
Expand Up @@ -727,7 +727,9 @@ module.exports = function (expect) {
};
} else if (typeof nextArg === 'function') {
expected[key] = function (s) {
return nextArg(s, index);
return nextArg._expectIt
? nextArg(s, expect.context)
: nextArg(s, index);
};
} else {
expected[key] = nextArg;
Expand Down Expand Up @@ -911,7 +913,7 @@ module.exports = function (expect) {

expect.addAssertion('<binaryArray> to [exhaustively] satisfy <expect.it>', function (expect, subject, value) {
return expect.withError(function () {
return value(subject);
return value(subject, expect.context);
}, function (e) {
expect.fail({
diff: function (output, diff, inspect, equal) {
Expand All @@ -922,6 +924,13 @@ module.exports = function (expect) {
});
});

expect.addAssertion('<UnexpectedError> to [exhaustively] satisfy <function>', function (expect, subject, value) {
return expect.promise(function () {
subject.serializeMessage(expect.outputFormat());
return value(subject);
});
});

expect.addAssertion('<any|Error> to [exhaustively] satisfy <function>', function (expect, subject, value) {
return expect.promise(function () {
return value(subject);
Expand Down Expand Up @@ -966,7 +975,7 @@ module.exports = function (expect) {

expect.addAssertion('<any> to [exhaustively] satisfy [assertion] <expect.it>', function (expect, subject, value) {
return expect.withError(function () {
return value(subject);
return value(subject, expect.context);
}, function (e) {
expect.fail({
diff: function (output) {
Expand Down Expand Up @@ -1237,7 +1246,10 @@ module.exports = function (expect) {
keys.forEach(function (key, index) {
promiseByKey[key] = expect.promise(function () {
var valueKeyType = expect.findTypeOf(value[key]);
if (valueKeyType.is('function')) {
if (valueKeyType.is('expect.it')) {
expect.context.thisObject = subject;
return value[key](subject[key], expect.context);
} else if (valueKeyType.is('function')) {
return value[key](subject[key]);
} else {
return expect(subject[key], 'to [exhaustively] satisfy', value[key]);
Expand Down Expand Up @@ -1433,12 +1445,18 @@ module.exports = function (expect) {
expect.argsOutput[0] = function (output) {
output.appendItems(args, ', ');
};
return expect.shift(subject.apply(subject, args));

var thisObject = expect.context.thisObject || null;

return expect.shift(subject.apply(thisObject, args));
});

expect.addAssertion('<function> [when] called <assertion?>', function (expect, subject) {
expect.errorMode = 'nested';
return expect.shift(subject.call(subject));

var thisObject = expect.context.thisObject || null;

return expect.shift(subject.call(thisObject));
});

function instantiate(Constructor, args) {
Expand Down
15 changes: 11 additions & 4 deletions lib/createWrappedExpectProto.js
Expand Up @@ -64,13 +64,13 @@ module.exports = function createWrappedExpectProto(unexpected) {
diff: unexpected.diff,
getType: unexpected.getType,
output: unexpected.output,
outputFormat: unexpected.outputFormat,
outputFormat: unexpected.outputFormat.bind(unexpected),
format: unexpected.format,
withError: unexpected.withError,

fail: function () {
var args = arguments;
var expect = this._context.expect;
var expect = this.context.expect;
this.callInNestedContext(function () {
expect.fail.apply(expect, args);
});
Expand All @@ -92,11 +92,14 @@ module.exports = function createWrappedExpectProto(unexpected) {
},
callInNestedContext: function (callback) {
var that = this;
var expect = that._context.expect;
var context = that.context;
var expect = context.expect;
context.level++;
try {
var result = oathbreaker(callback());
if (isPendingPromise(result)) {
result = result.then(undefined, function (e) {
context.level--;
if (e && e._isUnexpected) {
var wrappedError = new UnexpectedError(that, e);
wrappedError.originalError = e.originalError;
Expand All @@ -105,14 +108,18 @@ module.exports = function createWrappedExpectProto(unexpected) {
throw e;
});
} else if (!result || typeof result.then !== 'function') {
context.level--;
result = makePromise.resolve(result);
} else {
context.level--;
}
return addAdditionalPromiseMethods(result, that.execute, that.subject);
} catch (e) {
context.level--;
if (e && e._isUnexpected) {
var wrappedError = new UnexpectedError(that, e);
wrappedError.originalError = e.originalError;
if (that._context.serializeErrorsFromWrappedExpect) {
if (that.context.level === 0) {
expect.setErrorMessage(wrappedError);
}
throw wrappedError;
Expand Down

0 comments on commit f39e747

Please sign in to comment.