// Big thanks to V8 folks for test ideas. // v8/test/mjsunit/harmony/collections.js var Assertion = expect().constructor; Assertion.addMethod('theSameSet', function (otherArray) { var array = this._obj; expect(Array.isArray(array)).to.equal(true); expect(Array.isArray(otherArray)).to.equal(true); var diff = array.filter(function (value) { return otherArray.every(function (otherValue) { var areBothNaN = typeof value === 'number' && typeof otherValue === 'number' && value !== value && otherValue !== otherValue; return !areBothNaN && value !== otherValue; }); }); this.assert( diff.length === 0, 'expected #{this} to be equal to #{exp} (as sets, i.e. no order)', array, otherArray ); }); var $iterator$ = typeof Symbol === 'function' ? Symbol.iterator : void 0; if (!$iterator$ && typeof Set === 'function') { $iterator$ = typeof Set['@@iterator'] === 'function' ? '@@iterator' : '_es6-shim iterator_'; } Assertion.addMethod('iterations', function (expected) { var iterator = this._obj[$iterator$](); expect(Array.isArray(expected)).to.equal(true); var expectedValues = expected.slice(); var result; do { result = iterator.next(); expect(result.value).to.eql(expectedValues.shift()); } while (!result.done); }); describe('Set', function () { var functionsHaveNames = (function foo() {}).name === 'foo'; var ifFunctionsHaveNamesIt = functionsHaveNames ? it : xit; var ifShimIt = (typeof process !== 'undefined' && process.env.NO_ES6_SHIM) ? it.skip : it; var range = function (from, to) { var result = []; for (var value = from; value < to; value++) { result.push(value); } return result; }; var prototypePropIsEnumerable = Object.prototype.propertyIsEnumerable.call(function () {}, 'prototype'); var expectNotEnumerable = function (object) { if (prototypePropIsEnumerable && typeof object === 'function') { expect(Object.keys(object)).to.eql(['prototype']); } else { expect(Object.keys(object)).to.eql([]); } }; var Sym = typeof Symbol === 'undefined' ? {} : Symbol; var isSymbol = function (sym) { return typeof Sym === 'function' && typeof sym === 'symbol'; }; var ifSymbolIteratorIt = isSymbol(Sym.iterator) ? it : xit; var testSet = function (set, key) { expect(set.has(key)).to.equal(false); expect(set['delete'](key)).to.equal(false); expect(set.add(key)).to.equal(set); expect(set.has(key)).to.equal(true); expect(set['delete'](key)).to.equal(true); expect(set.has(key)).to.equal(false); expect(set.add(key)).to.equal(set); // add it back }; if (typeof Set === 'undefined') { return it('exists', function () { expect(typeof Set).to.equal('function'); }); } var set; beforeEach(function () { set = new Set(); }); afterEach(function () { set = null; }); it('set iteration', function () { expect(set.add('a')).to.equal(set); expect(set.add('b')).to.equal(set); expect(set.add('c')).to.equal(set); expect(set.add('d')).to.equal(set); var keys = []; var iterator = set.keys(); keys.push(iterator.next().value); expect(set['delete']('a')).to.equal(true); expect(set['delete']('b')).to.equal(true); expect(set['delete']('c')).to.equal(true); expect(set.add('e')).to.equal(set); keys.push(iterator.next().value); keys.push(iterator.next().value); expect(iterator.next().done).to.equal(true); expect(set.add('f')).to.equal(set); expect(iterator.next().done).to.equal(true); expect(keys).to.eql(['a', 'd', 'e']); }); ifShimIt('is on the exported object', function () { var exported = require('../'); expect(exported.Set).to.equal(Set); }); it('should exist in global namespace', function () { expect(typeof Set).to.equal('function'); }); it('has the right arity', function () { expect(Set).to.have.property('length', 0); }); it('returns the set from #add() for chaining', function () { expect(set.add({})).to.equal(set); }); it('should return false when deleting an item not in the set', function () { expect(set.has('a')).to.equal(false); expect(set['delete']('a')).to.equal(false); }); it('should accept an iterable as argument', function () { testSet(set, 'a'); testSet(set, 'b'); var set2 = new Set(set); expect(set2.has('a')).to.equal(true); expect(set2.has('b')).to.equal(true); expect(set2).to.have.iterations(['a', 'b']); }); it('accepts an array as an argument', function () { var arr = ['a', 'b', 'c']; var setFromArray = new Set(arr); expect(setFromArray).to.have.iterations(['a', 'b', 'c']); }); it('should not be callable without "new"', function () { expect(Set).to['throw'](TypeError); }); it('should be subclassable', function () { if (!Object.setPrototypeOf) { return; } // skip test if on IE < 11 var MySet = function MySet() { var actualSet = new Set(['a', 'b']); Object.setPrototypeOf(actualSet, MySet.prototype); return actualSet; }; Object.setPrototypeOf(MySet, Set); MySet.prototype = Object.create(Set.prototype, { constructor: { value: MySet } }); var mySet = new MySet(); testSet(mySet, 'c'); testSet(mySet, 'd'); expect(mySet).to.have.iterations(['a', 'b', 'c', 'd']); }); it('should has valid getter and setter calls', function () { ['add', 'has', 'delete'].forEach(function (method) { expect(function () { set[method]({}); }).to.not['throw'](); }); }); it('uses SameValueZero even on a Set of size > 4', function () { var firstFour = [1, 2, 3, 4]; var fourSet = new Set(firstFour); expect(fourSet.size).to.equal(4); expect(fourSet.has(-0)).to.equal(false); expect(fourSet.has(0)).to.equal(false); fourSet.add(-0); expect(fourSet.size).to.equal(5); expect(fourSet.has(0)).to.equal(true); expect(fourSet.has(-0)).to.equal(true); }); it('should work as expected', function () { // Run this test twice, one with the "fast" implementation (which only // allows string and numeric keys) and once with the "slow" impl. [true, false].forEach(function (slowkeys) { set = new Set(); range(1, 20).forEach(function (number) { if (slowkeys) { testSet(set, {}); } testSet(set, number); testSet(set, number / 100); testSet(set, 'key-' + number); testSet(set, String(number)); if (slowkeys) { testSet(set, Object(String(number))); } }); var testkeys = [+0, Infinity, -Infinity, NaN]; if (slowkeys) { testkeys.push(true, false, null, undefined); } testkeys.forEach(function (number) { testSet(set, number); testSet(set, String(number)); }); testSet(set, ''); // -0 and +0 should be the same key (Set uses SameValueZero) expect(set.has(-0)).to.equal(true); expect(set['delete'](+0)).to.equal(true); testSet(set, -0); expect(set.has(+0)).to.equal(true); // verify that properties of Object don't peek through. [ 'hasOwnProperty', 'constructor', 'toString', 'isPrototypeOf', '__proto__', '__parent__', '__count__' ].forEach(function (prop) { testSet(set, prop); }); }); }); describe('#size', function () { it('returns the expected size', function () { expect(set.add(1)).to.equal(set); expect(set.add(5)).to.equal(set); expect(set.size).to.equal(2); }); }); describe('#clear()', function () { ifFunctionsHaveNamesIt('has the right name', function () { expect(Set.prototype.clear).to.have.property('name', 'clear'); }); it('is not enumerable', function () { expect(Set.prototype).ownPropertyDescriptor('clear').to.have.property('enumerable', false); }); it('has the right arity', function () { expect(Set.prototype.clear).to.have.property('length', 0); }); it('clears a Set with only primitives', function () { expect(set.add(1)).to.equal(set); expect(set.size).to.equal(1); expect(set.add(5)).to.equal(set); expect(set.size).to.equal(2); expect(set.has(5)).to.equal(true); set.clear(); expect(set.size).to.equal(0); expect(set.has(5)).to.equal(false); }); it('clears a Set with primitives and objects', function () { expect(set.add(1)).to.equal(set); expect(set.size).to.equal(1); var obj = {}; expect(set.add(obj)).to.equal(set); expect(set.size).to.equal(2); expect(set.has(obj)).to.equal(true); set.clear(); expect(set.size).to.equal(0); expect(set.has(obj)).to.equal(false); }); }); describe('#keys()', function () { if (!Object.prototype.hasOwnProperty.call(Set.prototype, 'keys')) { return it('exists', function () { expect(Set.prototype).to.have.property('keys'); }); } it('is the same object as #values()', function () { expect(Set.prototype.keys).to.equal(Set.prototype.values); }); ifFunctionsHaveNamesIt('has the right name', function () { expect(Set.prototype.keys).to.have.property('name', 'values'); }); it('is not enumerable', function () { expect(Set.prototype).ownPropertyDescriptor('keys').to.have.property('enumerable', false); }); it('has the right arity', function () { expect(Set.prototype.keys).to.have.property('length', 0); }); }); describe('#values()', function () { if (!Object.prototype.hasOwnProperty.call(Set.prototype, 'values')) { return it('exists', function () { expect(Set.prototype).to.have.property('values'); }); } ifFunctionsHaveNamesIt('has the right name', function () { expect(Set.prototype.values).to.have.property('name', 'values'); }); it('is not enumerable', function () { expect(Set.prototype).ownPropertyDescriptor('values').to.have.property('enumerable', false); }); it('has the right arity', function () { expect(Set.prototype.values).to.have.property('length', 0); }); it('throws when called on a non-Set', function () { var expectedMessage = /^(Method )?Set.prototype.values called on incompatible receiver |^values method called on incompatible |^Cannot create a Set value iterator for a non-Set object.$|^Set.prototype.values: 'this' is not a Set object$|^std_Set_iterator method called on incompatible \w+$/; var nonSets = [true, false, 'abc', NaN, new Map([[1, 2]]), { a: true }, [1], Object('abc'), Object(NaN)]; nonSets.forEach(function (nonSet) { expect(function () { return Set.prototype.values.call(nonSet); }).to['throw'](TypeError, expectedMessage); }); }); }); describe('#entries()', function () { if (!Object.prototype.hasOwnProperty.call(Set.prototype, 'entries')) { return it('exists', function () { expect(Set.prototype).to.have.property('entries'); }); } ifFunctionsHaveNamesIt('has the right name', function () { expect(Set.prototype.entries).to.have.property('name', 'entries'); }); it('is not enumerable', function () { expect(Set.prototype).ownPropertyDescriptor('entries').to.have.property('enumerable', false); }); it('has the right arity', function () { expect(Set.prototype.entries).to.have.property('length', 0); }); }); describe('#has()', function () { if (!Object.prototype.hasOwnProperty.call(Set.prototype, 'has')) { return it('exists', function () { expect(Set.prototype).to.have.property('has'); }); } ifFunctionsHaveNamesIt('has the right name', function () { expect(Set.prototype.has).to.have.property('name', 'has'); }); it('is not enumerable', function () { expect(Set.prototype).ownPropertyDescriptor('has').to.have.property('enumerable', false); }); it('has the right arity', function () { expect(Set.prototype.has).to.have.property('length', 1); }); }); it('should allow NaN values as keys', function () { expect(set.has(NaN)).to.equal(false); expect(set.has(NaN + 1)).to.equal(false); expect(set.has(23)).to.equal(false); expect(set.add(NaN)).to.equal(set); expect(set.has(NaN)).to.equal(true); expect(set.has(NaN + 1)).to.equal(true); expect(set.has(23)).to.equal(false); }); it('should not have [[Enumerable]] props', function () { expectNotEnumerable(Set); expectNotEnumerable(Set.prototype); expectNotEnumerable(new Set()); }); it('should not have an own constructor', function () { var s = new Set(); expect(s).not.to.haveOwnPropertyDescriptor('constructor'); expect(s.constructor).to.equal(Set); }); it('should allow common ecmascript idioms', function () { expect(set instanceof Set).to.equal(true); expect(typeof Set.prototype.add).to.equal('function'); expect(typeof Set.prototype.has).to.equal('function'); expect(typeof Set.prototype['delete']).to.equal('function'); }); it('should have a unique constructor', function () { expect(Set.prototype).to.not.equal(Object.prototype); }); describe('has an iterator that works with Array.from', function () { if (!Object.prototype.hasOwnProperty.call(Array, 'from')) { return it('requires Array.from to exist', function () { expect(Array).to.have.property('from'); }); } var values = [1, NaN, false, true, null, undefined, 'a']; it('works with the full set', function () { expect(new Set(values)).to.have.iterations(values); }); it('works with Set#keys()', function () { expect(new Set(values).keys()).to.have.iterations(values); }); it('works with Set#values()', function () { expect(new Set(values).values()).to.have.iterations(values); }); it('works with Set#entries()', function () { expect(new Set(values).entries()).to.have.iterations([ [1, 1], [NaN, NaN], [false, false], [true, true], [null, null], [undefined, undefined], ['a', 'a'] ]); }); }); ifSymbolIteratorIt('has the right default iteration function', function () { // fixed in Webkit https://bugs.webkit.org/show_bug.cgi?id=143838 expect(Set.prototype).to.have.property(Sym.iterator, Set.prototype.values); }); it('should preserve insertion order', function () { var arr1 = ['d', 'a', 'b']; var arr2 = [3, 2, 'z', 'a', 1]; var arr3 = [3, 2, 'z', {}, 'a', 1]; [arr1, arr2, arr3].forEach(function (array) { expect(new Set(array)).to.have.iterations(array); }); }); describe('#forEach', function () { var setToIterate; beforeEach(function () { setToIterate = new Set(); expect(setToIterate.add('a')).to.equal(setToIterate); expect(setToIterate.add('b')).to.equal(setToIterate); expect(setToIterate.add('c')).to.equal(setToIterate); }); afterEach(function () { setToIterate = null; }); ifFunctionsHaveNamesIt('has the right name', function () { expect(Set.prototype.forEach).to.have.property('name', 'forEach'); }); it('is not enumerable', function () { expect(Set.prototype).ownPropertyDescriptor('forEach').to.have.property('enumerable', false); }); it('has the right arity', function () { expect(Set.prototype.forEach).to.have.property('length', 1); }); it('should be iterable via forEach', function () { var expectedSet = ['a', 'b', 'c']; var foundSet = []; setToIterate.forEach(function (value, alsoValue, entireSet) { expect(entireSet).to.equal(setToIterate); expect(value).to.equal(alsoValue); foundSet.push(value); }); expect(foundSet).to.eql(expectedSet); }); it('should iterate over empty keys', function () { var setWithEmptyKeys = new Set(); var expectedKeys = [{}, null, undefined, '', NaN, 0]; expectedKeys.forEach(function (key) { expect(setWithEmptyKeys.add(key)).to.equal(setWithEmptyKeys); }); var foundKeys = []; setWithEmptyKeys.forEach(function (value, key, entireSet) { expect([key]).to.be.theSameSet([value]); // handles NaN correctly expect(entireSet.has(key)).to.equal(true); foundKeys.push(key); }); expect(foundKeys).to.be.theSameSet(expectedKeys); }); it('should support the thisArg', function () { var context = function () {}; setToIterate.forEach(function () { expect(this).to.equal(context); }, context); }); it('should have a length of 1', function () { expect(Set.prototype.forEach.length).to.equal(1); }); it('should not revisit modified keys', function () { var hasModifiedA = false; setToIterate.forEach(function (value, key) { if (!hasModifiedA && key === 'a') { expect(setToIterate.add('a')).to.equal(setToIterate); hasModifiedA = true; } else { expect(key).not.to.equal('a'); } }); }); it('visits keys added in the iterator', function () { var hasAdded = false; var hasFoundD = false; setToIterate.forEach(function (value, key) { if (!hasAdded) { expect(setToIterate.add('d')).to.equal(setToIterate); hasAdded = true; } else if (key === 'd') { hasFoundD = true; } }); expect(hasFoundD).to.equal(true); }); it('visits keys added in the iterator when there is a deletion (slow path)', function () { var hasSeenFour = false; var setToMutate = new Set(); expect(setToMutate.add({})).to.equal(setToMutate); // force use of the slow O(N) implementation expect(setToMutate.add('0')).to.equal(setToMutate); setToMutate.forEach(function (value, key) { if (key === '0') { expect(setToMutate['delete']('0')).to.equal(true); expect(setToMutate.add('4')).to.equal(setToMutate); } else if (key === '4') { hasSeenFour = true; } }); expect(hasSeenFour).to.equal(true); }); it('visits keys added in the iterator when there is a deletion (fast path)', function () { var hasSeenFour = false; var setToMutate = new Set(); expect(setToMutate.add('0')).to.equal(setToMutate); setToMutate.forEach(function (value, key) { if (key === '0') { expect(setToMutate['delete']('0')).to.equal(true); expect(setToMutate.add('4')).to.equal(setToMutate); } else if (key === '4') { hasSeenFour = true; } }); expect(hasSeenFour).to.equal(true); }); it('does not visit keys deleted before a visit', function () { var hasVisitedC = false; var hasDeletedC = false; setToIterate.forEach(function (value, key) { if (key === 'c') { hasVisitedC = true; } if (!hasVisitedC && !hasDeletedC) { hasDeletedC = setToIterate['delete']('c'); expect(hasDeletedC).to.equal(true); } }); expect(hasVisitedC).to.equal(false); }); it('should work after deletion of the current key', function () { var expectedSet = { a: 'a', b: 'b', c: 'c' }; var foundSet = {}; setToIterate.forEach(function (value, key) { foundSet[key] = value; expect(setToIterate['delete'](key)).to.equal(true); }); expect(foundSet).to.eql(expectedSet); }); it('should convert key -0 to +0', function () { var zeroSet = new Set(); var result = []; expect(zeroSet.add(-0)).to.equal(zeroSet); zeroSet.forEach(function (key) { result.push(String(1 / key)); }); expect(zeroSet.add(1)).to.equal(zeroSet); expect(zeroSet.add(0)).to.equal(zeroSet); // shouldn't cause reordering zeroSet.forEach(function (key) { result.push(String(1 / key)); }); expect(result.join(', ')).to.equal( 'Infinity, Infinity, 1' ); }); }); it('Set.prototype.size should throw TypeError', function () { // see https://github.com/paulmillr/es6-shim/issues/176 expect(function () { return Set.prototype.size; }).to['throw'](TypeError); expect(function () { return Set.prototype.size; }).to['throw'](TypeError); }); it.skip('should throw proper errors when user invokes methods with wrong types of receiver', function () { }); });