Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow child expect to use assertions, types and styles created in a subsequently created clone #726

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 46 additions & 14 deletions lib/createTopLevelExpect.js
Original file line number Diff line number Diff line change
Expand Up @@ -1186,7 +1186,8 @@ expectPrototype.lookupAssertionRule = function (
subject,
testDescriptionString,
args,
requireAssertionSuffix
requireAssertionSuffix,
originatingExpect
) {
if (typeof testDescriptionString !== 'string') {
throw new Error(
Expand All @@ -1196,6 +1197,13 @@ expectPrototype.lookupAssertionRule = function (
let handlers;
let instance = this;
while (instance) {
if (
instance &&
originatingExpect &&
originatingExpect._isCloneOf(instance)
) {
instance = originatingExpect;
}
const instanceHandlers = instance.assertions[testDescriptionString];
if (instanceHandlers) {
handlers = handlers
Expand Down Expand Up @@ -1303,7 +1311,8 @@ expectPrototype._createWrappedExpect = function (
args,
testDescriptionString,
context,
forwardedFlags
forwardedFlags,
originatingExpect
) {
const flags = extend({}, forwardedFlags, assertionRule.flags);
const parentExpect = this;
Expand Down Expand Up @@ -1338,7 +1347,8 @@ expectPrototype._createWrappedExpect = function (
subject,
testDescriptionString,
args,
wrappedExpect.flags
wrappedExpect.flags,
originatingExpect
)
);
}
Expand Down Expand Up @@ -1380,7 +1390,8 @@ expectPrototype._executeExpect = function (
subject,
testDescriptionString,
args,
forwardedFlags
forwardedFlags,
originatingExpect
) {
if (forwardedFlags) {
testDescriptionString = utils.forwardFlags(
Expand All @@ -1391,7 +1402,9 @@ expectPrototype._executeExpect = function (
let assertionRule = this.lookupAssertionRule(
subject,
testDescriptionString,
args
args,
undefined,
originatingExpect
);

if (!assertionRule) {
Expand All @@ -1407,7 +1420,8 @@ expectPrototype._executeExpect = function (
subject,
prefix,
argsWithAssertionPrepended,
true
true,
originatingExpect
);
if (assertionRule) {
// Found the longest prefix of the string that yielded a suitable assertion for the given subject and args
Expand All @@ -1434,11 +1448,12 @@ expectPrototype._executeExpect = function (
}

if (assertionRule.expect && assertionRule.expect !== this._topLevelExpect) {
return assertionRule.expect._expect(context, [
subject,
testDescriptionString,
...args,
]);
return assertionRule.expect._expect(
context,
[subject, testDescriptionString, ...args],
undefined,
originatingExpect || this
);
}

const wrappedExpect = this._createWrappedExpect(
Expand All @@ -1447,13 +1462,19 @@ expectPrototype._executeExpect = function (
args,
testDescriptionString,
context,
forwardedFlags
forwardedFlags,
originatingExpect
);

return oathbreaker(assertionRule.handler(wrappedExpect, subject, ...args));
};

expectPrototype._expect = function (context, args, forwardedFlags) {
expectPrototype._expect = function (
context,
args,
forwardedFlags,
originatingExpect
) {
const subject = args[0];
const testDescriptionString = args[1];

Expand All @@ -1474,7 +1495,8 @@ expectPrototype._expect = function (context, args, forwardedFlags) {
subject,
testDescriptionString,
Array.prototype.slice.call(args, 2),
forwardedFlags
forwardedFlags,
originatingExpect
);
if (utils.isPromise(result)) {
result = wrapPromiseIfNecessary(result);
Expand Down Expand Up @@ -1572,6 +1594,7 @@ expectPrototype.clone = function () {
format: this.outputFormat(),
installedPlugins: [].concat(this.installedPlugins),
});
expect._clonedFromExpect = this;
// Install the hooks:
expect._expect = this._expect;
// Make sure that changes to the parent's preferredWidth doesn't propagate:
Expand Down Expand Up @@ -1728,6 +1751,15 @@ expectPrototype.standardErrorMessage = function (output, options) {
);
};

expectPrototype._isCloneOf = function (otherExpect) {
for (let instance = this; instance; instance = instance._clonedFromExpect) {
if (instance._clonedFromExpect === otherExpect) {
return true;
}
}
return false;
};

expectPrototype._callInNestedContext = function (callback) {
this._assertWrappedExpect();
try {
Expand Down
69 changes: 68 additions & 1 deletion test/api/child.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ describe('#child', () => {
});

childExpect.addType({
name: 'yadda-no-qoutes',
name: 'yadda-no-quotes',
identify: (obj) => /^yaddayadda/.test(obj),
inspect: (value, depth, output) => {
output.text(value);
Expand Down Expand Up @@ -339,4 +339,71 @@ describe('#child', () => {
'expected >>yaddayaddafoo<< to be short gibberish'
);
});

describe('when the parent gets cloned after the child was created', () => {
it('should allow assertions in the child to use types defined only in the clone', function () {
childExpect.exportAssertion('<string> to foo', function (
expect,
subject
) {
expect(subject, 'to be', 'foo');
});

const parentExpectClone = parentExpect.clone();
parentExpectClone.addType({
name: 'yadda',
identify: (obj) => /^yadda$/.test(obj),
inspect: (value, depth, output) => {
return output.text('>>').text(value).text('<<');
},
});
expect(
() => parentExpectClone('yadda', 'to foo'),
'to throw',
'expected >>yadda<< to foo'
);
});

it('should allow assertions in the child to use assertions defined only in the clone', function () {
childExpect.exportAssertion('<string> to foo', function (
expect,
subject
) {
expect.errorMode = 'nested';
expect(subject, 'to bar');
});

const parentExpectClone = parentExpect.clone();

parentExpectClone.addAssertion('<string> to bar', (expect) =>
expect(false, 'to be', true)
);
expect(
() => parentExpectClone('yadda', 'to foo'),
'to throw',
"expected 'yadda' to foo\n expected 'yadda' to bar"
);
});

it('should allow assertions in the child to use styles defined only in the clone', function () {
childExpect.exportAssertion('<string> to foo', function (expect) {
expect.fail({
diff(output) {
return output.fancyQuotes('blabla');
},
});
});

const parentExpectClone = parentExpect.clone();
parentExpectClone.addStyle('fancyQuotes', function (text) {
this.text('>>').text(text).text('<<');
});

expect(
() => parentExpectClone('yadda', 'to foo'),
'to throw',
"expected 'yadda' to foo\n\n>>blabla<<"
);
});
});
});