Repository: unexpectedjs/unexpected
Pull request #696 (2020-02-09 00:00:39)

Add support for symbols in <object> to have property/properties...
As discussed on https://github.com/unexpectedjs/unexpected/pull/671

papandreou: Oh yeah, thanks. I forgot that there was an issue about it.

Patch:
diff --git a/lib/assertions.js b/lib/assertions.js
index 3b3d10e6..019af699 100644
--- a/lib/assertions.js
+++ b/lib/assertions.js
@@ -266,7 +266,7 @@ module.exports = expect => {
   );
 
   expect.addAssertion(
-    '<object> [not] to have own property <string>',
+    '<object> [not] to have own property <string|Symbol>',
     (expect, subject, key) => {
       expect(
         Object.prototype.hasOwnProperty.call(subject, key),
@@ -277,7 +277,7 @@ module.exports = expect => {
   );
 
   expect.addAssertion(
-    '<object> to have (enumerable|configurable|writable) property <string>',
+    '<object> to have (enumerable|configurable|writable) property <string|Symbol>',
     (expect, subject, key) => {
       const attribute = expect.alternations[0];
       const descriptor = Object.getOwnPropertyDescriptor(subject, key);
@@ -288,7 +288,7 @@ module.exports = expect => {
   );
 
   expect.addAssertion(
-    '<object> [not] to have property <string>',
+    '<object> [not] to have property <string|Symbol>',
     (expect, subject, key) => {
       const subjectType = expect.findTypeOf(subject);
       const subjectKey = subjectType.is('function')
@@ -300,7 +300,7 @@ module.exports = expect => {
   );
 
   expect.addAssertion(
-    '<object> to have [own] property <string> <any>',
+    '<object> to have [own] property <string|Symbol> <any>',
     (expect, subject, key, expectedPropertyValue) =>
       expect(subject, 'to have [own] property', key).then(
         actualPropertyValue => {
@@ -320,20 +320,15 @@ module.exports = expect => {
   expect.addAssertion(
     '<object> [not] to [only] have [own] properties <array>',
     (expect, subject, propertyNames) => {
-      const unsupportedPropertyNames = [];
-      propertyNames.forEach(propertyName => {
-        if (
-          typeof propertyName !== 'string' &&
-          typeof propertyName !== 'number'
-        ) {
-          unsupportedPropertyNames.push(propertyName);
-        }
+      const unsupportedPropertyNames = propertyNames.filter(propertyName => {
+        const type = typeof propertyName;
+        return type !== 'string' && type !== 'number' && type !== 'symbol';
       });
       if (unsupportedPropertyNames.length > 0) {
         expect.errorMode = 'nested';
         expect.fail(function() {
           this.error(
-            'All expected properties must be passed as strings or numbers, but these are not:'
+            'All expected properties must be passed as strings, symbols, or numbers, but these are not:'
           ).indentLines();
           unsupportedPropertyNames.forEach(function(propertyName) {
             this.nl()
@@ -443,7 +438,13 @@ module.exports = expect => {
         );
       } else {
         propertyNames.forEach(propertyName => {
-          expect(subject, '[not] to have [own] property', String(propertyName));
+          expect(
+            subject,
+            '[not] to have [own] property',
+            typeof propertyName === 'number'
+              ? String(propertyName)
+              : propertyName
+          );
         });
       }
     }


Test patch:
diff --git a/test/assertions/to-have-properties.spec.js b/test/assertions/to-have-properties.spec.js
index 2ede6871..788766ec 100644
--- a/test/assertions/to-have-properties.spec.js
+++ b/test/assertions/to-have-properties.spec.js
@@ -252,7 +252,7 @@ describe('to have properties assertion', () => {
         },
         'to error',
         'expected { true: 123 } to have properties [ true ]\n' +
-          '  All expected properties must be passed as strings or numbers, but these are not:\n' +
+          '  All expected properties must be passed as strings, symbols, or numbers, but these are not:\n' +
           '    true'
       );
     });
@@ -271,7 +271,7 @@ describe('to have properties assertion', () => {
         'to error',
         'expected { foo: 123 }\n' +
           "to have properties [ { toString: function toString() { return 'foo'; } } ]\n" +
-          '  All expected properties must be passed as strings or numbers, but these are not:\n' +
+          '  All expected properties must be passed as strings, symbols, or numbers, but these are not:\n' +
           "    { toString: function toString() { return 'foo'; } }"
       );
     });
@@ -283,7 +283,7 @@ describe('to have properties assertion', () => {
         },
         'to error',
         'expected { foo: 123 } to have properties [ true, false ]\n' +
-          '  All expected properties must be passed as strings or numbers, but these are not:\n' +
+          '  All expected properties must be passed as strings, symbols, or numbers, but these are not:\n' +
           '    true\n' +
           '    false'
       );
@@ -392,4 +392,43 @@ describe('to have properties assertion', () => {
       );
     });
   });
+
+  if (typeof Symbol === 'function') {
+    describe('with symbols', function() {
+      it('should pass if the object has the given symbol(s)', () => {
+        const symbol = Symbol('foo');
+        expect({ [symbol]: 123 }, 'to have properties', [symbol]);
+      });
+
+      it('should fail if the object is missing the given symbol(s)', () => {
+        const symbolFoo = Symbol('foo');
+        const symbolBar = Symbol('bar');
+        expect(
+          () => expect({ [symbolFoo]: 123 }, 'to have properties', [symbolBar]),
+          'to throw',
+          "expected { [Symbol('foo')]: 123 } to have properties [ Symbol('bar') ]"
+        );
+      });
+
+      describe('and the own flag', function() {
+        it('should pass if the object has the given own symbol(s)', () => {
+          const symbol = Symbol('foo');
+          expect({ [symbol]: 123 }, 'to have own properties', [symbol]);
+        });
+
+        it('should fail if the object is missing the given symbol(s)', () => {
+          const symbolFoo = Symbol('foo');
+          const symbolBar = Symbol('bar');
+          expect(
+            () =>
+              expect({ [symbolFoo]: 123 }, 'to have own properties', [
+                symbolBar
+              ]),
+            'to throw',
+            "expected { [Symbol('foo')]: 123 } to have own properties [ Symbol('bar') ]"
+          );
+        });
+      });
+    });
+  }
 });
diff --git a/test/assertions/to-have-property.spec.js b/test/assertions/to-have-property.spec.js
index 64fb6290..e8070089 100644
--- a/test/assertions/to-have-property.spec.js
+++ b/test/assertions/to-have-property.spec.js
@@ -88,8 +88,8 @@ describe('to have property assertion', () => {
         '  The assertion does not have a matching signature for:\n' +
         '    <null> to have property <string>\n' +
         '  did you mean:\n' +
-        '    <object> [not] to have property <string>\n' +
-        '    <object> to have [own] property <string> <any>'
+        '    <object> [not] to have property <string|Symbol>\n' +
+        '    <object> to have [own] property <string|Symbol> <any>'
     );
 
     expect(
@@ -124,7 +124,7 @@ describe('to have property assertion', () => {
         '  The assertion does not have a matching signature for:\n' +
         '    <null> not to have property <string> <string>\n' +
         '  did you mean:\n' +
-        '    <object> [not] to have property <string>'
+        '    <object> [not] to have property <string|Symbol>'
     );
 
     expect(
@@ -137,7 +137,7 @@ describe('to have property assertion', () => {
         '  The assertion does not have a matching signature for:\n' +
         '    <null> not to have own property <string> <string>\n' +
         '  did you mean:\n' +
-        '    <object> [not] to have own property <string>'
+        '    <object> [not] to have own property <string|Symbol>'
     );
   });
 
@@ -151,7 +151,7 @@ describe('to have property assertion', () => {
         '  The assertion does not have a matching signature for:\n' +
         '    <object> not to have property <string> <string>\n' +
         '  did you mean:\n' +
-        '    <object> [not] to have property <string>'
+        '    <object> [not] to have property <string|Symbol>'
     );
 
     expect(
@@ -163,7 +163,7 @@ describe('to have property assertion', () => {
         '  The assertion does not have a matching signature for:\n' +
         '    <object> not to have own property <string> <string>\n' +
         '  did you mean:\n' +
-        '    <object> [not] to have own property <string>'
+        '    <object> [not] to have own property <string|Symbol>'
     );
   });
 
@@ -192,4 +192,83 @@ describe('to have property assertion', () => {
       );
     });
   });
+
+  if (typeof Symbol === 'function') {
+    describe('with symbols', function() {
+      describe('to have property', function() {
+        it('should pass when the object contains the symbol', function() {
+          const symbol = Symbol('foo');
+          expect({ [symbol]: 123 }, 'to have property', symbol);
+        });
+
+        it('should fail when the object does not contain the symbol', function() {
+          const symbol = Symbol('foo');
+          expect(
+            () => expect({ bar: 123 }, 'to have property', symbol),
+            'to throw',
+            "expected { bar: 123 } to have property Symbol('foo')"
+          );
+        });
+      });
+
+      describe('to have own property', function() {
+        it('should pass when the object contains the symbol', function() {
+          const symbol = Symbol('foo');
+          expect({ [symbol]: 123 }, 'to have own property', symbol);
+        });
+
+        it('should fail when the object does not contain the symbol', function() {
+          const symbol = Symbol('foo');
+          expect(
+            () => expect({ bar: 123 }, 'to have own property', symbol),
+            'to throw',
+            "expected { bar: 123 } to have own property Symbol('foo')"
+          );
+        });
+
+        describe('with expected value', function() {
+          it('should pass when the object contains the symbol with the given value', function() {
+            const symbol = Symbol('foo');
+            expect({ [symbol]: 123 }, 'to have own property', symbol, 123);
+          });
+
+          it('should fail when the object does not contain the symbol', function() {
+            const symbol = Symbol('foo');
+            expect(
+              () => expect({ bar: 123 }, 'to have own property', symbol, 123),
+              'to throw',
+              "expected { bar: 123 } to have own property Symbol('foo'), 123"
+            );
+          });
+
+          it('should fail when the object contains the symbol, but with a different value', function() {
+            const symbol = Symbol('foo');
+            expect(
+              () =>
+                expect({ [symbol]: 456 }, 'to have own property', symbol, 123),
+              'to throw',
+              "expected { [Symbol('foo')]: 456 }\n" +
+                "to have own property Symbol('foo') with a value of 123"
+            );
+          });
+        });
+      });
+
+      describe('to have enumerable(/configurable/writable) property', function() {
+        it('should pass when the object contains the symbol', function() {
+          const symbol = Symbol('foo');
+          expect({ [symbol]: 123 }, 'to have enumerable property', symbol);
+        });
+
+        it('should fail when the object does not contain the symbol', function() {
+          const symbol = Symbol('foo');
+          expect(
+            () => expect({ bar: 123 }, 'to have enumerable property', symbol),
+            'to throw',
+            "expected { bar: 123 } to have enumerable property Symbol('foo')"
+          );
+        });
+      });
+    });
+  }
 });