From e17e47a44d6e1d55f6a984d20b60264e7318c051 Mon Sep 17 00:00:00 2001 From: Andreas Lind <andreas.lind@peakon.com> Date: Wed, 13 May 2020 22:26:22 +0200 Subject: [PATCH 1/2] Add failing tests --- test/api/child.spec.js | 69 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/test/api/child.spec.js b/test/api/child.spec.js index 6c8a2ed6e..2ed4df82e 100644 --- a/test/api/child.spec.js +++ b/test/api/child.spec.js @@ -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); @@ -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<<" + ); + }); + }); }); From 931ceab0d7e0cebeef061dda64ceb8e81a4c75bc Mon Sep 17 00:00:00 2001 From: Andreas Lind <andreas.lind@peakon.com> Date: Thu, 14 May 2020 00:26:18 +0200 Subject: [PATCH 2/2] Hack in a fix for the assertion bit --- lib/createTopLevelExpect.js | 60 ++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/lib/createTopLevelExpect.js b/lib/createTopLevelExpect.js index dd9489672..b92426ea9 100644 --- a/lib/createTopLevelExpect.js +++ b/lib/createTopLevelExpect.js @@ -1186,7 +1186,8 @@ expectPrototype.lookupAssertionRule = function ( subject, testDescriptionString, args, - requireAssertionSuffix + requireAssertionSuffix, + originatingExpect ) { if (typeof testDescriptionString !== 'string') { throw new Error( @@ -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 @@ -1303,7 +1311,8 @@ expectPrototype._createWrappedExpect = function ( args, testDescriptionString, context, - forwardedFlags + forwardedFlags, + originatingExpect ) { const flags = extend({}, forwardedFlags, assertionRule.flags); const parentExpect = this; @@ -1338,7 +1347,8 @@ expectPrototype._createWrappedExpect = function ( subject, testDescriptionString, args, - wrappedExpect.flags + wrappedExpect.flags, + originatingExpect ) ); } @@ -1380,7 +1390,8 @@ expectPrototype._executeExpect = function ( subject, testDescriptionString, args, - forwardedFlags + forwardedFlags, + originatingExpect ) { if (forwardedFlags) { testDescriptionString = utils.forwardFlags( @@ -1391,7 +1402,9 @@ expectPrototype._executeExpect = function ( let assertionRule = this.lookupAssertionRule( subject, testDescriptionString, - args + args, + undefined, + originatingExpect ); if (!assertionRule) { @@ -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 @@ -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( @@ -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]; @@ -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); @@ -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: @@ -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 {