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 {