Skip to content

Commit

Permalink
Fix duplicate handling in members assertion
Browse files Browse the repository at this point in the history
  • Loading branch information
meeber committed Jun 25, 2016
1 parent c92fe8f commit 3fdb542
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
Original file line number Diff line number Diff line change
Expand Up @@ -1571,15 +1571,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 @@ -1594,7 +1612,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 @@ -1637,19 +1655,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 @@ -1658,7 +1673,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
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,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 @@ -1185,6 +1186,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 @@ -1195,6 +1201,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 @@ -1207,6 +1214,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 @@ -1215,6 +1226,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 @@ -1224,6 +1236,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 @@ -1232,6 +1248,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 @@ -1241,6 +1258,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 @@ -1305,6 +1326,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 @@ -1322,6 +1344,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
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,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 @@ -1383,17 +1384,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 @@ -1407,16 +1418,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 @@ -1431,6 +1465,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 @@ -1443,7 +1478,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 @@ -1458,6 +1499,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
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,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 @@ -1229,8 +1230,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 @@ -1242,10 +1248,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 @@ -1260,6 +1297,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 @@ -1272,7 +1310,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 @@ -1287,6 +1331,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 3fdb542

Please sign in to comment.