Repository: unexpectedjs/unexpected
Pull request #577 (2019-01-11 00:28:39)

Avoid deprecation warnings in node.js 10 because we have methods called inspect
https://github.com/nodejs/node/issues/15549 :roll_eyes:

In node.js 10+ the custom inspection function for our promises works, but causes a deprecation warning to be logged:

![node_10_promise_inspect](https://user-images.githubusercontent.com/373545/51073211-d73f3180-166d-11e9-8e50-a897ff2e8d7e.png)

Ditto for `Unexpected#inspect`:

![node_10_expect_inspect](https://user-images.githubusercontent.com/373545/51073216-e1613000-166d-11e9-95e4-f31ebfe29254.png)

The fix is to provide the custom inspection function via a symbol exported as `require('util').inspect.custom` instead. But because we're also using the `inspect` method name for other things in Unexpected, we're forced to provide the symbol method on all objects that have an `inspect` method. This PR fixes that up so we provide both in node versions where the custom symbol exists.


Patch:
diff --git a/lib/Unexpected.js b/lib/Unexpected.js
index dfb764bd..ab0d7eff 100644
--- a/lib/Unexpected.js
+++ b/lib/Unexpected.js
@@ -13,6 +13,7 @@ const defaultDepth = require('./defaultDepth');
 const createWrappedExpectProto = require('./createWrappedExpectProto');
 const AssertionString = require('./AssertionString');
 const throwIfNonUnexpectedError = require('./throwIfNonUnexpectedError');
+const nodeJsCustomInspect = require('./nodeJsCustomInspect');
 
 function isAssertionArg({ type }) {
   return type.is('assertion');
@@ -73,6 +74,12 @@ const anyType = {
   }
 };
 
+if (nodeJsCustomInspect !== 'inspect') {
+  anyType[nodeJsCustomInspect] = function() {
+    return `type: ${this.name}`;
+  };
+}
+
 function Unexpected(options = {}) {
   this.assertions = options.assertions || {};
   this.typeByName = options.typeByName || { any: anyType };
@@ -358,6 +365,10 @@ Unexpected.prototype.inspect = function(obj, depth, outputOrFormat) {
   );
 };
 
+if (nodeJsCustomInspect !== 'inspect') {
+  Unexpected.prototype[nodeJsCustomInspect] = Unexpected.prototype.inspect;
+}
+
 Unexpected.prototype.expandTypeAlternations = function(assertion) {
   const that = this;
   function createPermutations(args, i) {
@@ -819,6 +830,10 @@ Unexpected.prototype.addType = function(type, childUnexpected) {
     );
   };
 
+  if (nodeJsCustomInspect !== 'inspect') {
+    extendedBaseType[nodeJsCustomInspect] = extendedBaseType.inspect;
+  }
+
   extendedBaseType.diff = (actual, expected, output) => {
     if (!output || !output.isMagicPen) {
       throw new Error(
@@ -844,6 +859,13 @@ Unexpected.prototype.addType = function(type, childUnexpected) {
   });
   const originalInspect = extendedType.inspect;
 
+  // Prevent node.js' util.inspect from complaining about our inspect method:
+  if (nodeJsCustomInspect !== 'inspect') {
+    extendedType[nodeJsCustomInspect] = function() {
+      return `type: ${type.name}`;
+    };
+  }
+
   extendedType.inspect = function(obj, depth, output, inspect) {
     if (arguments.length < 2 || (!output || !output.isMagicPen)) {
       return `type: ${type.name}`;
@@ -1019,6 +1041,11 @@ function installExpectMethods(unexpected) {
   expect.it = unexpected.it.bind(unexpected);
   expect.equal = unexpected.equal.bind(unexpected);
   expect.inspect = unexpected.inspect.bind(unexpected);
+  if (nodeJsCustomInspect !== 'inspect') {
+    expect[nodeJsCustomInspect] = unexpected[nodeJsCustomInspect].bind(
+      unexpected
+    );
+  }
   expect.findTypeOf = unexpected.findTypeOf; // Already bound
   expect.fail = (...args) => {
     try {
diff --git a/lib/createWrappedExpectProto.js b/lib/createWrappedExpectProto.js
index f21402bf..2b96c814 100644
--- a/lib/createWrappedExpectProto.js
+++ b/lib/createWrappedExpectProto.js
@@ -5,6 +5,7 @@ const wrapPromiseIfNecessary = require('./wrapPromiseIfNecessary');
 const oathbreaker = require('./oathbreaker');
 const UnexpectedError = require('./UnexpectedError');
 const utils = require('./utils');
+const nodeJsCustomInspect = require('./nodeJsCustomInspect');
 
 function isAssertionArg({ type }) {
   return type.is('assertion');
@@ -254,6 +255,10 @@ module.exports = function createWrappedExpectProto(unexpected) {
     }
   };
 
+  if (nodeJsCustomInspect !== 'inspect') {
+    wrappedExpectProto[nodeJsCustomInspect] = unexpected[nodeJsCustomInspect];
+  }
+
   if (Object.__defineGetter__) {
     Object.defineProperty(wrappedExpectProto, 'subjectType', {
       enumerable: true,
diff --git a/lib/index.js b/lib/index.js
index f825a7bc..c09f88e0 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -6,7 +6,9 @@ module.exports = require('./Unexpected')
   .freeze();
 
 // Add an inspect method to all the promises we return that will make the REPL, console.log, and util.inspect render it nicely in node.js:
-require('unexpected-bluebird').prototype.inspect = function() {
+require('unexpected-bluebird').prototype[
+  require('./nodeJsCustomInspect')
+] = function() {
   return module.exports
     .createOutput(require('magicpen').defaultFormat)
     .appendInspected(this)
diff --git a/lib/nodeJsCustomInspect.js b/lib/nodeJsCustomInspect.js
new file mode 100644
index 00000000..c2d3c3ce
--- /dev/null
+++ b/lib/nodeJsCustomInspect.js
@@ -0,0 +1,8 @@
+module.exports = 'inspect';
+
+try {
+  const util = require('' + 'util');
+  if (util && typeof util.inspect === 'function' && util.inspect.custom) {
+    module.exports = util.inspect.custom;
+  }
+} catch (err) {}


Test patch:
diff --git a/test/api/promise.spec.js b/test/api/promise.spec.js
index 45b07f6b..ab45b3c7 100644
--- a/test/api/promise.spec.js
+++ b/test/api/promise.spec.js
@@ -258,6 +258,15 @@ describe('expect.promise', () => {
     });
   });
 
+  let inspectMethodName = 'inspect';
+  // In node.js 10+ the custom inspect method name has to be given as a symbol
+  // or we'll get ugly deprecation warnings logged to the console.
+  try {
+    const util = require('util');
+    if (util.inspect.custom) {
+      inspectMethodName = util.inspect.custom;
+    }
+  } catch (err) {}
   describe('#inspect', () => {
     var originalDefaultFormat = expect.output.constructor.defaultFormat;
     beforeEach(() => {
@@ -273,7 +282,7 @@ describe('expect.promise', () => {
           .promise(function() {
             expect(2, 'to equal', 2);
           })
-          .inspect(),
+          [inspectMethodName](),
         'to equal',
         'Promise (fulfilled)'
       );
@@ -285,7 +294,7 @@ describe('expect.promise', () => {
           .promise(function() {
             return 123;
           })
-          .inspect(),
+          [inspectMethodName](),
         'to equal',
         'Promise (fulfilled) => 123'
       );
@@ -298,7 +307,11 @@ describe('expect.promise', () => {
         'to equal',
         'foo'
       );
-      expect(asyncPromise.inspect(), 'to equal', 'Promise (pending)');
+      expect(
+        asyncPromise[inspectMethodName](),
+        'to equal',
+        'Promise (pending)'
+      );
       return asyncPromise;
     });
 
@@ -308,7 +321,7 @@ describe('expect.promise', () => {
       });
 
       return promise.caught(function() {
-        expect(promise.inspect(), 'to equal', 'Promise (rejected)');
+        expect(promise[inspectMethodName](), 'to equal', 'Promise (rejected)');
       });
     });
 
@@ -321,7 +334,7 @@ describe('expect.promise', () => {
 
       return promise.caught(function() {
         expect(
-          promise.inspect(),
+          promise[inspectMethodName](),
           'to equal',
           "Promise (rejected) => Error('argh')"
         );