Skip to content

Commit b625497

Browse files
authored
Merge pull request #1012 from meeber/fix-include-types
fix: `.include` to work with all objects
2 parents 8fa24f2 + c01cf30 commit b625497

4 files changed

Lines changed: 89 additions & 48 deletions

File tree

‎lib/chai/core/assertions.js‎

Lines changed: 56 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -469,53 +469,18 @@ module.exports = function (chai, _) {
469469

470470
function include (val, msg) {
471471
if (msg) flag(this, 'message', msg);
472-
473-
_.expectTypes(this, [
474-
'array', 'object', 'string',
475-
'map', 'set', 'weakset',
476-
]);
477-
472+
478473
var obj = flag(this, 'object')
479-
, objType = _.type(obj).toLowerCase();
480-
481-
// This block is for asserting a subset of properties in an object.
482-
if (objType === 'object') {
483-
var props = Object.keys(val)
484-
, negate = flag(this, 'negate')
485-
, firstErr = null
486-
, numErrs = 0;
487-
488-
props.forEach(function (prop) {
489-
var propAssertion = new Assertion(obj);
490-
_.transferFlags(this, propAssertion, true);
491-
flag(propAssertion, 'lockSsfi', true);
492-
493-
if (!negate || props.length === 1) {
494-
propAssertion.property(prop, val[prop]);
495-
return;
496-
}
497-
498-
try {
499-
propAssertion.property(prop, val[prop]);
500-
} catch (err) {
501-
if (!_.checkError.compatibleConstructor(err, AssertionError)) throw err;
502-
if (firstErr === null) firstErr = err;
503-
numErrs++;
504-
}
505-
}, this);
506-
507-
// When validating .not.include with multiple properties, we only want
508-
// to throw an assertion error if all of the properties are included,
509-
// in which case we throw the first property assertion error that we
510-
// encountered.
511-
if (negate && props.length > 1 && numErrs === props.length) throw firstErr;
474+
, objType = _.type(obj).toLowerCase()
475+
, flagMsg = flag(this, 'message')
476+
, negate = flag(this, 'negate')
477+
, ssfi = flag(this, 'ssfi')
478+
, isDeep = flag(this, 'deep')
479+
, descriptor = isDeep ? 'deep ' : '';
512480

513-
return;
514-
}
481+
flagMsg = flagMsg ? flagMsg + ': ' : '';
515482

516-
var isDeep = flag(this, 'deep')
517-
, descriptor = isDeep ? 'deep ' : ''
518-
, included = false;
483+
var included = false;
519484

520485
switch (objType) {
521486
case 'string':
@@ -524,10 +489,6 @@ module.exports = function (chai, _) {
524489

525490
case 'weakset':
526491
if (isDeep) {
527-
var flagMsg = flag(this, 'message')
528-
, ssfi = flag(this, 'ssfi');
529-
flagMsg = flagMsg ? flagMsg + ': ' : '';
530-
531492
throw new AssertionError(
532493
flagMsg + 'unable to use .deep.include with WeakSet',
533494
undefined,
@@ -564,6 +525,53 @@ module.exports = function (chai, _) {
564525
included = obj.indexOf(val) !== -1;
565526
}
566527
break;
528+
529+
default:
530+
// This block is for asserting a subset of properties in an object.
531+
// `_.expectTypes` isn't used here because `.include` should work with
532+
// objects with a custom `@@toStringTag`.
533+
if (val !== Object(val)) {
534+
throw new AssertionError(
535+
flagMsg + 'object tested must be an array, a map, an object,'
536+
+ ' a set, a string, or a weakset, but ' + objType + ' given',
537+
undefined,
538+
ssfi
539+
);
540+
}
541+
542+
var props = Object.keys(val)
543+
, firstErr = null
544+
, numErrs = 0;
545+
546+
props.forEach(function (prop) {
547+
var propAssertion = new Assertion(obj);
548+
_.transferFlags(this, propAssertion, true);
549+
flag(propAssertion, 'lockSsfi', true);
550+
551+
if (!negate || props.length === 1) {
552+
propAssertion.property(prop, val[prop]);
553+
return;
554+
}
555+
556+
try {
557+
propAssertion.property(prop, val[prop]);
558+
} catch (err) {
559+
if (!_.checkError.compatibleConstructor(err, AssertionError)) {
560+
throw err;
561+
}
562+
if (firstErr === null) firstErr = err;
563+
numErrs++;
564+
}
565+
}, this);
566+
567+
// When validating .not.include with multiple properties, we only want
568+
// to throw an assertion error if all of the properties are included,
569+
// in which case we throw the first property assertion error that we
570+
// encountered.
571+
if (negate && props.length > 1 && numErrs === props.length) {
572+
throw firstErr;
573+
}
574+
return;
567575
}
568576

569577
// Assert inclusion in collection or substring in a string.

‎test/assert.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,17 @@ describe('assert', function () {
638638
assert.include('', '');
639639
assert.include([ 1, 2, 3], 3);
640640

641+
// .include should work with Error objects and objects with a custom
642+
// `@@toStringTag`.
643+
assert.include(new Error('foo'), {message: 'foo'});
644+
if (typeof Symbol !== 'undefined'
645+
&& typeof Symbol.toStringTag !== 'undefined') {
646+
var customObj = {a: 1};
647+
customObj[Symbol.toStringTag] = 'foo';
648+
649+
assert.include(customObj, {a: 1});
650+
}
651+
641652
var obj1 = {a: 1}
642653
, obj2 = {b: 2};
643654
assert.include([obj1, obj2], obj1);

‎test/expect.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,6 +1884,17 @@ describe('expect', function () {
18841884

18851885
expect({a: 1}).to.include({'toString': Object.prototype.toString});
18861886

1887+
// .include should work with Error objects and objects with a custom
1888+
// `@@toStringTag`.
1889+
expect(new Error('foo')).to.include({message: 'foo'});
1890+
if (typeof Symbol !== 'undefined'
1891+
&& typeof Symbol.toStringTag !== 'undefined') {
1892+
var customObj = {a: 1};
1893+
customObj[Symbol.toStringTag] = 'foo';
1894+
1895+
expect(customObj).to.include({a: 1});
1896+
}
1897+
18871898
var obj1 = {a: 1}
18881899
, obj2 = {b: 2};
18891900
expect([obj1, obj2]).to.include(obj1);

‎test/should.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,6 +1540,17 @@ describe('should', function() {
15401540
['foo', 'bar'].should.not.include('baz');
15411541
['foo', 'bar'].should.not.include(1);
15421542

1543+
// .include should work with Error objects and objects with a custom
1544+
// `@@toStringTag`.
1545+
(new Error('foo')).should.include({message: 'foo'});
1546+
if (typeof Symbol !== 'undefined'
1547+
&& typeof Symbol.toStringTag !== 'undefined') {
1548+
var customObj = {a: 1};
1549+
customObj[Symbol.toStringTag] = 'foo';
1550+
1551+
customObj.should.include({a: 1});
1552+
}
1553+
15431554
({a: 1}).should.include({'toString': Object.prototype.toString});
15441555

15451556
var obj1 = {a: 1}

0 commit comments

Comments
 (0)