Skip to content

Commit

Permalink
Merge pull request #739 from meeber/duplicate-members
Browse files Browse the repository at this point in the history
Fix duplicate handling in members assertion
  • Loading branch information
keithamus committed Jun 27, 2016
2 parents 387dca2 + 3fdb542 commit cc7799c
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 12 deletions.
39 changes: 27 additions & 12 deletions lib/chai/core/assertions.js
Expand Up @@ -1576,15 +1576,33 @@ module.exports = function (chai, _) {
Assertion.addMethod('closeTo', closeTo);
Assertion.addMethod('approximately', closeTo);

function isSubsetOf(subset, superset, cmp, ordered) {
// Note: Duplicates are ignored if testing for inclusion instead of sameness.
function isSubsetOf(subset, superset, cmp, contains, ordered) {
if (!contains) {
if (subset.length !== superset.length) return false;
superset = superset.slice();
}

return subset.every(function(elem, idx) {
if (ordered) return cmp ? cmp(elem, superset[idx]) : elem === superset[idx];
if (!cmp) return superset.indexOf(elem) !== -1;

return superset.some(function(elem2) {
return cmp(elem, elem2);
if (!cmp) {
var matchIdx = superset.indexOf(elem);
if (matchIdx === -1) return false;

// Remove match from superset so not counted twice if duplicate in subset.
if (!contains) superset.splice(matchIdx, 1);
return true;
}

return superset.some(function(elem2, matchIdx) {
if (!cmp(elem, elem2)) return false;

// Remove match from superset so not counted twice if duplicate in subset.
if (!contains) superset.splice(matchIdx, 1);
return true;
});
})
});
}

/**
Expand All @@ -1599,7 +1617,7 @@ module.exports = function (chai, _) {
* expect([1, 2, 3]).to.not.have.members([5, 1, 3]);
*
* If the `contains` flag is set via `.include` or `.contain`, instead asserts
* that the target is a superset of `set`.
* that the target is a superset of `set`. Duplicates are ignored.
*
* expect([1, 2, 3]).to.include.members([2, 1]);
* expect([1, 2, 3]).to.not.include.members([5, 1]);
Expand Down Expand Up @@ -1642,19 +1660,16 @@ module.exports = function (chai, _) {
new Assertion(obj).to.be.an('array');
new Assertion(subset).to.be.an('array');

var contains = flag(this, 'contains');
var ordered = flag(this, 'ordered');

var failMsg, failNegateMsg, lengthCheck;

if (flag(this, 'contains')) {
lengthCheck = true;

if (contains) {
var subject = ordered ? 'an ordered superset' : 'a superset';
failMsg = 'expected #{this} to be ' + subject + ' of #{exp}';
failNegateMsg = 'expected #{this} to not be ' + subject + ' of #{exp}';
} else {
lengthCheck = obj.length === subset.length;

var subject = ordered ? 'ordered members' : 'members';
failMsg = 'expected #{this} to have the same ' + subject + ' as #{exp}';
failNegateMsg = 'expected #{this} to not have the same ' + subject + ' as #{exp}';
Expand All @@ -1663,7 +1678,7 @@ module.exports = function (chai, _) {
var cmp = flag(this, 'deep') ? _.eql : undefined;

this.assert(
lengthCheck && isSubsetOf(subset, obj, cmp, ordered)
isSubsetOf(subset, obj, cmp, contains, ordered)
, failMsg
, failNegateMsg
, subset
Expand Down
23 changes: 23 additions & 0 deletions test/assert.js
Expand Up @@ -1237,6 +1237,7 @@ describe('assert', function () {
assert.sameMembers([], []);
assert.sameMembers([1, 2, 3], [3, 2, 1]);
assert.sameMembers([4, 2], [4, 2]);
assert.sameMembers([4, 2, 2], [4, 2, 2]);

err(function() {
assert.sameMembers([], [1, 2]);
Expand All @@ -1249,6 +1250,11 @@ describe('assert', function () {

it('notSameMembers', function() {
assert.notSameMembers([1, 2, 3], [2, 1, 5]);
assert.notSameMembers([1, 2, 3], [1, 2, 3, 3]);
assert.notSameMembers([1, 2], [1, 2, 2]);
assert.notSameMembers([1, 2, 2], [1, 2]);
assert.notSameMembers([1, 2, 2], [1, 2, 3]);
assert.notSameMembers([1, 2, 3], [1, 2, 2]);
assert.notSameMembers([{a: 1}], [{a: 1}]);

err(function() {
Expand All @@ -1259,6 +1265,7 @@ describe('assert', function () {
it('sameDeepMembers', function() {
assert.sameDeepMembers([ {b: 3}, {a: 2}, {c: 5} ], [ {c: 5}, {b: 3}, {a: 2} ], 'same deep members');
assert.sameDeepMembers([ {b: 3}, {a: 2}, 5, "hello" ], [ "hello", 5, {b: 3}, {a: 2} ], 'same deep members');
assert.sameDeepMembers([{a: 1}, {b: 2}, {b: 2}], [{a: 1}, {b: 2}, {b: 2}]);

err(function() {
assert.sameDeepMembers([ {b: 3} ], [ {c: 3} ])
Expand All @@ -1271,6 +1278,10 @@ describe('assert', function () {

it('notSameDeepMembers', function() {
assert.notSameDeepMembers([{a: 1}, {b: 2}, {c: 3}], [{b: 2}, {a: 1}, {f: 5}]);
assert.notSameDeepMembers([{a: 1}, {b: 2}], [{a: 1}, {b: 2}, {b: 2}]);
assert.notSameDeepMembers([{a: 1}, {b: 2}, {b: 2}], [{a: 1}, {b: 2}]);
assert.notSameDeepMembers([{a: 1}, {b: 2}, {b: 2}], [{a: 1}, {b: 2}, {c: 3}]);
assert.notSameDeepMembers([{a: 1}, {b: 2}, {c: 3}], [{a: 1}, {b: 2}, {b: 2}]);

err(function() {
assert.notSameDeepMembers([{a: 1}, {b: 2}, {c: 3}], [{b: 2}, {a: 1}, {c: 3}]);
Expand All @@ -1279,6 +1290,7 @@ describe('assert', function () {

it('sameOrderedMembers', function() {
assert.sameOrderedMembers([1, 2, 3], [1, 2, 3]);
assert.sameOrderedMembers([1, 2, 2], [1, 2, 2]);

err(function() {
assert.sameOrderedMembers([1, 2, 3], [2, 1, 3]);
Expand All @@ -1288,6 +1300,10 @@ describe('assert', function () {
it('notSameOrderedMembers', function() {
assert.notSameOrderedMembers([1, 2, 3], [2, 1, 3]);
assert.notSameOrderedMembers([1, 2, 3], [1, 2]);
assert.notSameOrderedMembers([1, 2], [1, 2, 2]);
assert.notSameOrderedMembers([1, 2, 2], [1, 2]);
assert.notSameOrderedMembers([1, 2, 2], [1, 2, 3]);
assert.notSameOrderedMembers([1, 2, 3], [1, 2, 2]);

err(function() {
assert.notSameOrderedMembers([1, 2, 3], [1, 2, 3]);
Expand All @@ -1296,6 +1312,7 @@ describe('assert', function () {

it('sameDeepOrderedMembers', function() {
assert.sameDeepOrderedMembers([{a: 1}, {b: 2}, {c: 3}], [{a: 1}, {b: 2}, {c: 3}]);
assert.sameDeepOrderedMembers([{a: 1}, {b: 2}, {b: 2}], [{a: 1}, {b: 2}, {b: 2}]);

err(function() {
assert.sameDeepOrderedMembers([{a: 1}, {b: 2}, {c: 3}], [{b: 2}, {a: 1}, {c: 3}]);
Expand All @@ -1305,6 +1322,10 @@ describe('assert', function () {
it('notSameDeepOrderedMembers', function() {
assert.notSameDeepOrderedMembers([{a: 1}, {b: 2}, {c: 3}], [{b: 2}, {a: 1}, {c: 3}]);
assert.notSameDeepOrderedMembers([{a: 1}, {b: 2}, {c: 3}], [{a: 1}, {b: 2}, {f: 5}]);
assert.notSameDeepOrderedMembers([{a: 1}, {b: 2}], [{a: 1}, {b: 2}, {b: 2}]);
assert.notSameDeepOrderedMembers([{a: 1}, {b: 2}, {b: 2}], [{a: 1}, {b: 2}]);
assert.notSameDeepOrderedMembers([{a: 1}, {b: 2}, {b: 2}], [{a: 1}, {b: 2}, {c: 3}]);
assert.notSameDeepOrderedMembers([{a: 1}, {b: 2}, {c: 3}], [{a: 1}, {b: 2}, {b: 2}]);

err(function() {
assert.notSameDeepOrderedMembers([{a: 1}, {b: 2}, {c: 3}], [{a: 1}, {b: 2}, {c: 3}]);
Expand Down Expand Up @@ -1369,6 +1390,7 @@ describe('assert', function () {
it('notIncludeOrderedMembers', function() {
assert.notIncludeOrderedMembers([1, 2, 3], [2, 1]);
assert.notIncludeOrderedMembers([1, 2, 3], [2, 3]);
assert.notIncludeOrderedMembers([1, 2, 3], [1, 2, 2]);

err(function() {
assert.notIncludeOrderedMembers([1, 2, 3], [1, 2]);
Expand All @@ -1386,6 +1408,7 @@ describe('assert', function () {
it('notIncludeDeepOrderedMembers', function() {
assert.notIncludeDeepOrderedMembers([{a: 1}, {b: 2}, {c: 3}], [{b: 2}, {a: 1}]);
assert.notIncludeDeepOrderedMembers([{a: 1}, {b: 2}, {c: 3}], [{a: 1}, {f: 5}]);
assert.notIncludeDeepOrderedMembers([{a: 1}, {b: 2}, {c: 3}], [{a: 1}, {b: 2}, {b: 2}]);

err(function() {
assert.notIncludeDeepOrderedMembers([{a: 1}, {b: 2}, {c: 3}], [{a: 1}, {b: 2}]);
Expand Down
42 changes: 42 additions & 0 deletions test/expect.js
Expand Up @@ -1383,6 +1383,7 @@ describe('expect', function () {
it('include.members', function() {
expect([1, 2, 3]).to.include.members([]);
expect([1, 2, 3]).to.include.members([3, 2]);
expect([1, 2, 3]).to.include.members([3, 2, 2]);
expect([1, 2, 3]).to.not.include.members([8, 4]);
expect([1, 2, 3]).to.not.include.members([1, 2, 3, 4]);
expect([{a: 1}]).to.not.include.members([{a: 1}]);
Expand All @@ -1399,17 +1400,27 @@ describe('expect', function () {
it('same.members', function() {
expect([5, 4]).to.have.same.members([4, 5]);
expect([5, 4]).to.have.same.members([5, 4]);
expect([5, 4, 4]).to.have.same.members([5, 4, 4]);
expect([5, 4]).to.not.have.same.members([]);
expect([5, 4]).to.not.have.same.members([6, 3]);
expect([5, 4]).to.not.have.same.members([5, 4, 2]);
expect([5, 4]).to.not.have.same.members([5, 4, 4]);
expect([5, 4, 4]).to.not.have.same.members([5, 4]);
expect([5, 4, 4]).to.not.have.same.members([5, 4, 3]);
expect([5, 4, 3]).to.not.have.same.members([5, 4, 4]);
});

it('members', function() {
expect([5, 4]).members([4, 5]);
expect([5, 4]).members([5, 4]);
expect([5, 4, 4]).members([5, 4, 4]);
expect([5, 4]).not.members([]);
expect([5, 4]).not.members([6, 3]);
expect([5, 4]).not.members([5, 4, 2]);
expect([5, 4]).not.members([5, 4, 4]);
expect([5, 4, 4]).not.members([5, 4]);
expect([5, 4, 4]).not.members([5, 4, 3]);
expect([5, 4, 3]).not.members([5, 4, 4]);
expect([{ id: 1 }]).not.members([{ id: 1 }]);

err(function() {
Expand All @@ -1423,16 +1434,39 @@ describe('expect', function () {

it('deep.members', function() {
expect([{ id: 1 }]).deep.members([{ id: 1 }]);
expect([{a: 1}, {b: 2}, {b: 2}]).deep.members([{a: 1}, {b: 2}, {b: 2}]);

expect([{ id: 2 }]).not.deep.members([{ id: 1 }]);
expect([{a: 1}, {b: 2}]).not.deep.members([{a: 1}, {b: 2}, {b: 2}]);
expect([{a: 1}, {b: 2}, {b: 2}]).not.deep.members([{a: 1}, {b: 2}]);
expect([{a: 1}, {b: 2}, {b: 2}]).not.deep.members([{a: 1}, {b: 2}, {c: 3}]);
expect([{a: 1}, {b: 2}, {c: 3}]).not.deep.members([{a: 1}, {b: 2}, {b: 2}]);

err(function(){
expect([{ id: 1 }]).deep.members([{ id: 2 }])
}, 'expected [ { id: 1 } ] to have the same members as [ { id: 2 } ]');
});

it('include.deep.members', function() {
expect([{a: 1}, {b: 2}, {c: 3}]).include.deep.members([{b: 2}, {a: 1}]);
expect([{a: 1}, {b: 2}, {c: 3}]).include.deep.members([{b: 2}, {a: 1}, {a: 1}]);
expect([{a: 1}, {b: 2}, {c: 3}]).not.include.deep.members([{b: 2}, {a: 1}, {f: 5}]);

err(function() {
expect([{a: 1}, {b: 2}, {c: 3}]).include.deep.members([{b: 2}, {a: 1}, {f: 5}]);
}, 'expected [ { a: 1 }, { b: 2 }, { c: 3 } ] to be a superset of [ { b: 2 }, { a: 1 }, { f: 5 } ]');
});

it('ordered.members', function() {
expect([1, 2, 3]).ordered.members([1, 2, 3]);
expect([1, 2, 2]).ordered.members([1, 2, 2]);

expect([1, 2, 3]).not.ordered.members([2, 1, 3]);
expect([1, 2, 3]).not.ordered.members([1, 2]);
expect([1, 2]).not.ordered.members([1, 2, 2]);
expect([1, 2, 2]).not.ordered.members([1, 2]);
expect([1, 2, 2]).not.ordered.members([1, 2, 3]);
expect([1, 2, 3]).not.ordered.members([1, 2, 2]);

err(function() {
expect([1, 2, 3]).ordered.members([2, 1, 3]);
Expand All @@ -1447,6 +1481,7 @@ describe('expect', function () {
expect([1, 2, 3]).include.ordered.members([1, 2]);
expect([1, 2, 3]).not.include.ordered.members([2, 1]);
expect([1, 2, 3]).not.include.ordered.members([2, 3]);
expect([1, 2, 3]).not.include.ordered.members([1, 2, 2]);

err(function() {
expect([1, 2, 3]).include.ordered.members([2, 1]);
Expand All @@ -1459,7 +1494,13 @@ describe('expect', function () {

it('deep.ordered.members', function() {
expect([{a: 1}, {b: 2}, {c: 3}]).deep.ordered.members([{a: 1}, {b: 2}, {c: 3}]);
expect([{a: 1}, {b: 2}, {b: 2}]).deep.ordered.members([{a: 1}, {b: 2}, {b: 2}]);

expect([{a: 1}, {b: 2}, {c: 3}]).not.deep.ordered.members([{b: 2}, {a: 1}, {c: 3}]);
expect([{a: 1}, {b: 2}]).not.deep.ordered.members([{a: 1}, {b: 2}, {b: 2}]);
expect([{a: 1}, {b: 2}, {b: 2}]).not.deep.ordered.members([{a: 1}, {b: 2}]);
expect([{a: 1}, {b: 2}, {b: 2}]).not.deep.ordered.members([{a: 1}, {b: 2}, {c: 3}]);
expect([{a: 1}, {b: 2}, {c: 3}]).not.deep.ordered.members([{a: 1}, {b: 2}, {b: 2}]);

err(function() {
expect([{a: 1}, {b: 2}, {c: 3}]).deep.ordered.members([{b: 2}, {a: 1}, {c: 3}]);
Expand All @@ -1474,6 +1515,7 @@ describe('expect', function () {
expect([{a: 1}, {b: 2}, {c: 3}]).include.deep.ordered.members([{a: 1}, {b: 2}]);
expect([{a: 1}, {b: 2}, {c: 3}]).not.include.deep.ordered.members([{b: 2}, {a: 1}]);
expect([{a: 1}, {b: 2}, {c: 3}]).not.include.deep.ordered.members([{b: 2}, {c: 3}]);
expect([{a: 1}, {b: 2}, {c: 3}]).not.include.deep.ordered.members([{a: 1}, {b: 2}, {b: 2}]);

err(function() {
expect([{a: 1}, {b: 2}, {c: 3}]).include.deep.ordered.members([{b: 2}, {a: 1}]);
Expand Down
45 changes: 45 additions & 0 deletions test/should.js
Expand Up @@ -1221,6 +1221,7 @@ describe('should', function() {
[1, 2, 3].should.include.members([3]);
[1, 2, 3].should.include.members([]);
[1, 2, 3].should.include.members([2, 1]);
[1, 2, 3].should.include.members([2, 1, 1]);

[1, 2, 3].should.not.include.members([999]);
[].should.not.include.members([23]);
Expand All @@ -1246,8 +1247,13 @@ describe('should', function() {
it('memberEquals', function() {
[1, 2, 3].should.have.same.members([3, 2, 1]);
[5, 4].should.have.same.members([5, 4]);
[5, 4, 4].should.have.same.members([5, 4, 4]);
[].should.have.same.members([]);

[5, 4].should.not.have.same.members([5, 4, 4]);
[5, 4, 4].should.not.have.same.members([5, 4]);
[5, 4, 4].should.not.have.same.members([5, 4, 3]);
[5, 4, 3].should.not.have.same.members([5, 4, 4]);
[{a: 1}].should.not.have.same.members([{a: 1}]);

err(function() {
Expand All @@ -1259,10 +1265,41 @@ describe('should', function() {
}, 'expected 4 to be an array');
});

it('deep.members', function() {
[{ id: 1 }].should.have.deep.members([{ id: 1 }]);
[{a: 1}, {b: 2}, {b: 2}].should.have.deep.members([{a: 1}, {b: 2}, {b: 2}]);

[{ id: 2 }].should.not.have.deep.members([{ id: 1 }]);
[{a: 1}, {b: 2}].should.not.have.deep.members([{a: 1}, {b: 2}, {b: 2}]);
[{a: 1}, {b: 2}, {b: 2}].should.not.have.deep.members([{a: 1}, {b: 2}]);
[{a: 1}, {b: 2}, {b: 2}].should.not.have.deep.members([{a: 1}, {b: 2}, {c: 3}]);
[{a: 1}, {b: 2}, {c: 3}].should.not.have.deep.members([{a: 1}, {b: 2}, {b: 2}]);

err(function(){
[{ id: 1 }].should.have.deep.members([{ id: 2 }])
}, 'expected [ { id: 1 } ] to have the same members as [ { id: 2 } ]');
});

it('include.deep.members', function() {
[{a: 1}, {b: 2}, {c: 3}].should.include.deep.members([{b: 2}, {a: 1}]);
[{a: 1}, {b: 2}, {c: 3}].should.include.deep.members([{b: 2}, {a: 1}, {a: 1}]);
[{a: 1}, {b: 2}, {c: 3}].should.not.include.deep.members([{b: 2}, {a: 1}, {f: 5}]);

err(function() {
[{a: 1}, {b: 2}, {c: 3}].should.include.deep.members([{b: 2}, {a: 1}, {f: 5}]);
}, 'expected [ { a: 1 }, { b: 2 }, { c: 3 } ] to be a superset of [ { b: 2 }, { a: 1 }, { f: 5 } ]');
});

it('ordered.members', function() {
[1, 2, 3].should.ordered.members([1, 2, 3]);
[1, 2, 2].should.ordered.members([1, 2, 2]);

[1, 2, 3].should.not.ordered.members([2, 1, 3]);
[1, 2, 3].should.not.ordered.members([1, 2]);
[1, 2].should.not.ordered.members([1, 2, 2]);
[1, 2, 2].should.not.ordered.members([1, 2]);
[1, 2, 2].should.not.ordered.members([1, 2, 3]);
[1, 2, 3].should.not.ordered.members([1, 2, 2]);

err(function() {
[1, 2, 3].should.ordered.members([2, 1, 3]);
Expand All @@ -1277,6 +1314,7 @@ describe('should', function() {
[1, 2, 3].should.include.ordered.members([1, 2]);
[1, 2, 3].should.not.include.ordered.members([2, 1]);
[1, 2, 3].should.not.include.ordered.members([2, 3]);
[1, 2, 3].should.not.include.ordered.members([1, 2, 2]);

err(function() {
[1, 2, 3].should.include.ordered.members([2, 1]);
Expand All @@ -1289,7 +1327,13 @@ describe('should', function() {

it('deep.ordered.members', function() {
[{a: 1}, {b: 2}, {c: 3}].should.deep.ordered.members([{a: 1}, {b: 2}, {c: 3}]);
[{a: 1}, {b: 2}, {b: 2}].should.deep.ordered.members([{a: 1}, {b: 2}, {b: 2}]);

[{a: 1}, {b: 2}, {c: 3}].should.not.deep.ordered.members([{b: 2}, {a: 1}, {c: 3}]);
[{a: 1}, {b: 2}].should.not.deep.ordered.members([{a: 1}, {b: 2}, {b: 2}]);
[{a: 1}, {b: 2}, {b: 2}].should.not.deep.ordered.members([{a: 1}, {b: 2}]);
[{a: 1}, {b: 2}, {b: 2}].should.not.deep.ordered.members([{a: 1}, {b: 2}, {c: 3}]);
[{a: 1}, {b: 2}, {c: 3}].should.not.deep.ordered.members([{a: 1}, {b: 2}, {b: 2}]);

err(function() {
[{a: 1}, {b: 2}, {c: 3}].should.deep.ordered.members([{b: 2}, {a: 1}, {c: 3}]);
Expand All @@ -1304,6 +1348,7 @@ describe('should', function() {
[{a: 1}, {b: 2}, {c: 3}].should.include.deep.ordered.members([{a: 1}, {b: 2}]);
[{a: 1}, {b: 2}, {c: 3}].should.not.include.deep.ordered.members([{b: 2}, {a: 1}]);
[{a: 1}, {b: 2}, {c: 3}].should.not.include.deep.ordered.members([{b: 2}, {c: 3}]);
[{a: 1}, {b: 2}, {c: 3}].should.not.include.deep.ordered.members([{a: 1}, {b: 2}, {b: 2}]);

err(function() {
[{a: 1}, {b: 2}, {c: 3}].should.include.deep.ordered.members([{b: 2}, {a: 1}]);
Expand Down

0 comments on commit cc7799c

Please sign in to comment.