604 lines
20 KiB
JavaScript
604 lines
20 KiB
JavaScript
// 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
|
|
);
|
|
});
|
|
|
|
Assertion.addMethod('entries', function (expected) {
|
|
var collection = this._obj;
|
|
|
|
expect(Array.isArray(expected)).to.equal(true);
|
|
var expectedEntries = expected.slice();
|
|
|
|
var iterator = collection.entries();
|
|
var result;
|
|
do {
|
|
result = iterator.next();
|
|
expect(result.value).to.be.eql(expectedEntries.shift());
|
|
} while (!result.done);
|
|
});
|
|
|
|
describe('Map', 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 range(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 testMapping = function (map, key, value) {
|
|
expect(map.has(key)).to.equal(false);
|
|
expect(map.get(key)).to.equal(undefined);
|
|
expect(map.set(key, value)).to.equal(map);
|
|
expect(map.get(key)).to.equal(value);
|
|
expect(map.has(key)).to.equal(true);
|
|
};
|
|
|
|
if (typeof Map === 'undefined') {
|
|
return it('exists', function () {
|
|
expect(typeof Map).to.equal('function');
|
|
});
|
|
}
|
|
|
|
var map;
|
|
beforeEach(function () {
|
|
map = new Map();
|
|
});
|
|
|
|
afterEach(function () {
|
|
map = null;
|
|
});
|
|
|
|
ifShimIt('is on the exported object', function () {
|
|
var exported = require('../');
|
|
expect(exported.Map).to.equal(Map);
|
|
});
|
|
|
|
it('should exist in global namespace', function () {
|
|
expect(typeof Map).to.equal('function');
|
|
});
|
|
|
|
it('should have the right arity', function () {
|
|
expect(Map).to.have.property('length', 0);
|
|
});
|
|
|
|
it('should has valid getter and setter calls', function () {
|
|
['get', 'set', 'has', 'delete'].forEach(function (method) {
|
|
expect(function () {
|
|
map[method]({});
|
|
}).to.not['throw']();
|
|
});
|
|
});
|
|
|
|
it('should accept an iterable as argument', function () {
|
|
testMapping(map, 'a', 'b');
|
|
testMapping(map, 'c', 'd');
|
|
var map2;
|
|
expect(function () { map2 = new Map(map); }).not.to['throw'](Error);
|
|
expect(map2).to.be.an.instanceOf(Map);
|
|
expect(map2.has('a')).to.equal(true);
|
|
expect(map2.has('c')).to.equal(true);
|
|
expect(map2).to.have.entries([['a', 'b'], ['c', 'd']]);
|
|
});
|
|
|
|
it('should throw with iterables that return primitives', function () {
|
|
expect(function () { return new Map('123'); }).to['throw'](TypeError);
|
|
expect(function () { return new Map([1, 2, 3]); }).to['throw'](TypeError);
|
|
expect(function () { return new Map(['1', '2', '3']); }).to['throw'](TypeError);
|
|
expect(function () { return new Map([true]); }).to['throw'](TypeError);
|
|
});
|
|
|
|
it('should not be callable without "new"', function () {
|
|
expect(Map).to['throw'](TypeError);
|
|
});
|
|
|
|
it('should be subclassable', function () {
|
|
if (!Object.setPrototypeOf) { return; } // skip test if on IE < 11
|
|
var MyMap = function MyMap() {
|
|
var testMap = new Map([['a', 'b']]);
|
|
Object.setPrototypeOf(testMap, MyMap.prototype);
|
|
return testMap;
|
|
};
|
|
Object.setPrototypeOf(MyMap, Map);
|
|
MyMap.prototype = Object.create(Map.prototype, {
|
|
constructor: { value: MyMap }
|
|
});
|
|
|
|
var myMap = new MyMap();
|
|
testMapping(myMap, 'c', 'd');
|
|
expect(myMap).to.have.entries([['a', 'b'], ['c', 'd']]);
|
|
});
|
|
|
|
it('uses SameValueZero even on a Map of size > 4', function () {
|
|
// Chrome 38-42, node 0.11/0.12, iojs 1/2 have a bug when the Map has a size > 4
|
|
var firstFour = [[1, 0], [2, 0], [3, 0], [4, 0]];
|
|
var fourMap = new Map(firstFour);
|
|
expect(fourMap.size).to.equal(4);
|
|
expect(fourMap.has(-0)).to.equal(false);
|
|
expect(fourMap.has(0)).to.equal(false);
|
|
|
|
fourMap.set(-0, fourMap);
|
|
|
|
expect(fourMap.has(0)).to.equal(true);
|
|
expect(fourMap.has(-0)).to.equal(true);
|
|
});
|
|
|
|
it('treats positive and negative zero the same', function () {
|
|
var value1 = {};
|
|
var value2 = {};
|
|
testMapping(map, +0, value1);
|
|
expect(map.has(-0)).to.equal(true);
|
|
expect(map.get(-0)).to.equal(value1);
|
|
expect(map.set(-0, value2)).to.equal(map);
|
|
expect(map.get(-0)).to.equal(value2);
|
|
expect(map.get(+0)).to.equal(value2);
|
|
});
|
|
|
|
it('should map values correctly', 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) {
|
|
map = new Map();
|
|
|
|
range(1, 20).forEach(function (number) {
|
|
if (slowkeys) { testMapping(map, number, {}); }
|
|
testMapping(map, number / 100, {});
|
|
testMapping(map, 'key-' + number, {});
|
|
testMapping(map, String(number), {});
|
|
if (slowkeys) { testMapping(map, Object(String(number)), {}); }
|
|
});
|
|
|
|
var testkeys = [Infinity, -Infinity, NaN];
|
|
if (slowkeys) {
|
|
testkeys.push(true, false, null, undefined);
|
|
}
|
|
testkeys.forEach(function (key) {
|
|
testMapping(map, key, {});
|
|
testMapping(map, String(key), {});
|
|
});
|
|
testMapping(map, '', {});
|
|
|
|
// verify that properties of Object don't peek through.
|
|
[
|
|
'hasOwnProperty',
|
|
'constructor',
|
|
'toString',
|
|
'isPrototypeOf',
|
|
'__proto__',
|
|
'__parent__',
|
|
'__count__'
|
|
].forEach(function (key) {
|
|
testMapping(map, key, {});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should map empty values correctly', function () {
|
|
testMapping(map, {}, true);
|
|
testMapping(map, null, true);
|
|
testMapping(map, undefined, true);
|
|
testMapping(map, '', true);
|
|
testMapping(map, NaN, true);
|
|
testMapping(map, 0, true);
|
|
});
|
|
|
|
it('should has correct querying behavior', function () {
|
|
var key = {};
|
|
testMapping(map, key, 'to-be-present');
|
|
expect(map.has(key)).to.equal(true);
|
|
expect(map.has({})).to.equal(false);
|
|
expect(map.set(key, void 0)).to.equal(map);
|
|
expect(map.get(key)).to.equal(undefined);
|
|
expect(map.has(key)).to.equal(true);
|
|
expect(map.has({})).to.equal(false);
|
|
});
|
|
|
|
it('should allow NaN values as keys', function () {
|
|
expect(map.has(NaN)).to.equal(false);
|
|
expect(map.has(NaN + 1)).to.equal(false);
|
|
expect(map.has(23)).to.equal(false);
|
|
expect(map.set(NaN, 'value')).to.equal(map);
|
|
expect(map.has(NaN)).to.equal(true);
|
|
expect(map.has(NaN + 1)).to.equal(true);
|
|
expect(map.has(23)).to.equal(false);
|
|
});
|
|
|
|
it('should not have [[Enumerable]] props', function () {
|
|
expectNotEnumerable(Map);
|
|
expectNotEnumerable(Map.prototype);
|
|
expectNotEnumerable(new Map());
|
|
});
|
|
|
|
it('should not have an own constructor', function () {
|
|
var m = new Map();
|
|
expect(m).not.to.haveOwnPropertyDescriptor('constructor');
|
|
expect(m.constructor).to.equal(Map);
|
|
});
|
|
|
|
it('should allow common ecmascript idioms', function () {
|
|
expect(map).to.be.an.instanceOf(Map);
|
|
expect(typeof Map.prototype.get).to.equal('function');
|
|
expect(typeof Map.prototype.set).to.equal('function');
|
|
expect(typeof Map.prototype.has).to.equal('function');
|
|
expect(typeof Map.prototype['delete']).to.equal('function');
|
|
});
|
|
|
|
it('should have a unique constructor', function () {
|
|
expect(Map.prototype).to.not.equal(Object.prototype);
|
|
});
|
|
|
|
describe('#clear()', function () {
|
|
ifFunctionsHaveNamesIt('has the right name', function () {
|
|
expect(Map.prototype.clear).to.have.property('name', 'clear');
|
|
});
|
|
|
|
it('is not enumerable', function () {
|
|
expect(Map.prototype).ownPropertyDescriptor('clear').to.have.property('enumerable', false);
|
|
});
|
|
|
|
it('has the right arity', function () {
|
|
expect(Map.prototype.clear).to.have.property('length', 0);
|
|
});
|
|
|
|
it('should have #clear method', function () {
|
|
expect(map.set(1, 2)).to.equal(map);
|
|
expect(map.set(5, 2)).to.equal(map);
|
|
expect(map.size).to.equal(2);
|
|
expect(map.has(5)).to.equal(true);
|
|
map.clear();
|
|
expect(map.size).to.equal(0);
|
|
expect(map.has(5)).to.equal(false);
|
|
});
|
|
});
|
|
|
|
describe('#keys()', function () {
|
|
if (!Object.prototype.hasOwnProperty.call(Map.prototype, 'keys')) {
|
|
return it('exists', function () {
|
|
expect(Map.prototype).to.have.property('keys');
|
|
});
|
|
}
|
|
|
|
ifFunctionsHaveNamesIt('has the right name', function () {
|
|
expect(Map.prototype.keys).to.have.property('name', 'keys');
|
|
});
|
|
|
|
it('is not enumerable', function () {
|
|
expect(Map.prototype).ownPropertyDescriptor('keys').to.have.property('enumerable', false);
|
|
});
|
|
|
|
it('has the right arity', function () {
|
|
expect(Map.prototype.keys).to.have.property('length', 0);
|
|
});
|
|
});
|
|
|
|
describe('#values()', function () {
|
|
if (!Object.prototype.hasOwnProperty.call(Map.prototype, 'values')) {
|
|
return it('exists', function () {
|
|
expect(Map.prototype).to.have.property('values');
|
|
});
|
|
}
|
|
|
|
ifFunctionsHaveNamesIt('has the right name', function () {
|
|
expect(Map.prototype.values).to.have.property('name', 'values');
|
|
});
|
|
|
|
it('is not enumerable', function () {
|
|
expect(Map.prototype).ownPropertyDescriptor('values').to.have.property('enumerable', false);
|
|
});
|
|
|
|
it('has the right arity', function () {
|
|
expect(Map.prototype.values).to.have.property('length', 0);
|
|
});
|
|
});
|
|
|
|
describe('#entries()', function () {
|
|
if (!Object.prototype.hasOwnProperty.call(Map.prototype, 'entries')) {
|
|
return it('exists', function () {
|
|
expect(Map.prototype).to.have.property('entries');
|
|
});
|
|
}
|
|
|
|
ifFunctionsHaveNamesIt('has the right name', function () {
|
|
expect(Map.prototype.entries).to.have.property('name', 'entries');
|
|
});
|
|
|
|
it('is not enumerable', function () {
|
|
expect(Map.prototype).ownPropertyDescriptor('entries').to.have.property('enumerable', false);
|
|
});
|
|
|
|
it('has the right arity', function () {
|
|
expect(Map.prototype.entries).to.have.property('length', 0);
|
|
});
|
|
|
|
it('throws when called on a non-Map', function () {
|
|
var expectedMessage = /^(Method )?Map.prototype.entries called on incompatible receiver |^entries method called on incompatible |^Cannot create a Map entry iterator for a non-Map object.|^Map\.prototype\.entries: 'this' is not a Map object$|^std_Map_iterator method called on incompatible \w+$/;
|
|
var nonMaps = [true, false, 'abc', NaN, new Set([1, 2]), { a: true }, [1], Object('abc'), Object(NaN)];
|
|
nonMaps.forEach(function (nonMap) {
|
|
expect(function () { return Map.prototype.entries.call(nonMap); }).to['throw'](TypeError, expectedMessage);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#size', function () {
|
|
it('throws TypeError when accessed directly', function () {
|
|
// see https://github.com/paulmillr/es6-shim/issues/176
|
|
expect(function () { return Map.prototype.size; }).to['throw'](TypeError);
|
|
expect(function () { return Map.prototype.size; }).to['throw'](TypeError);
|
|
});
|
|
|
|
it('is an accessor function on the prototype', function () {
|
|
expect(Map.prototype).ownPropertyDescriptor('size').to.have.property('get');
|
|
expect(typeof Object.getOwnPropertyDescriptor(Map.prototype, 'size').get).to.equal('function');
|
|
expect(new Map()).not.to.haveOwnPropertyDescriptor('size');
|
|
});
|
|
});
|
|
|
|
it('should return false when deleting a nonexistent key', function () {
|
|
expect(map.has('a')).to.equal(false);
|
|
expect(map['delete']('a')).to.equal(false);
|
|
});
|
|
|
|
it('should have keys, values and size props', function () {
|
|
expect(map.set('a', 1)).to.equal(map);
|
|
expect(map.set('b', 2)).to.equal(map);
|
|
expect(map.set('c', 3)).to.equal(map);
|
|
expect(typeof map.keys).to.equal('function');
|
|
expect(typeof map.values).to.equal('function');
|
|
expect(map.size).to.equal(3);
|
|
expect(map['delete']('a')).to.equal(true);
|
|
expect(map.size).to.equal(2);
|
|
});
|
|
|
|
it('should have an iterator that works with Array.from', function () {
|
|
expect(Array).to.have.property('from');
|
|
|
|
expect(map.set('a', 1)).to.equal(map);
|
|
expect(map.set('b', NaN)).to.equal(map);
|
|
expect(map.set('c', false)).to.equal(map);
|
|
expect(Array.from(map)).to.eql([['a', 1], ['b', NaN], ['c', false]]);
|
|
expect(Array.from(map.keys())).to.eql(['a', 'b', 'c']);
|
|
expect(Array.from(map.values())).to.eql([1, NaN, false]);
|
|
expect(map).to.have.entries(Array.from(map.entries()));
|
|
});
|
|
|
|
ifSymbolIteratorIt('has the right default iteration function', function () {
|
|
// fixed in Webkit https://bugs.webkit.org/show_bug.cgi?id=143838
|
|
expect(Map.prototype).to.have.property(Sym.iterator, Map.prototype.entries);
|
|
});
|
|
|
|
describe('#forEach', function () {
|
|
var mapToIterate;
|
|
|
|
beforeEach(function () {
|
|
mapToIterate = new Map();
|
|
expect(mapToIterate.set('a', 1)).to.equal(mapToIterate);
|
|
expect(mapToIterate.set('b', 2)).to.equal(mapToIterate);
|
|
expect(mapToIterate.set('c', 3)).to.equal(mapToIterate);
|
|
});
|
|
|
|
afterEach(function () {
|
|
mapToIterate = null;
|
|
});
|
|
|
|
ifFunctionsHaveNamesIt('has the right name', function () {
|
|
expect(Map.prototype.forEach).to.have.property('name', 'forEach');
|
|
});
|
|
|
|
it('is not enumerable', function () {
|
|
expect(Map.prototype).ownPropertyDescriptor('forEach').to.have.property('enumerable', false);
|
|
});
|
|
|
|
it('has the right arity', function () {
|
|
expect(Map.prototype.forEach).to.have.property('length', 1);
|
|
});
|
|
|
|
it('should be iterable via forEach', function () {
|
|
var expectedMap = {
|
|
a: 1,
|
|
b: 2,
|
|
c: 3
|
|
};
|
|
var foundMap = {};
|
|
mapToIterate.forEach(function (value, key, entireMap) {
|
|
expect(entireMap).to.equal(mapToIterate);
|
|
foundMap[key] = value;
|
|
});
|
|
expect(foundMap).to.eql(expectedMap);
|
|
});
|
|
|
|
it('should iterate over empty keys', function () {
|
|
var mapWithEmptyKeys = new Map();
|
|
var expectedKeys = [{}, null, undefined, '', NaN, 0];
|
|
expectedKeys.forEach(function (key) {
|
|
expect(mapWithEmptyKeys.set(key, true)).to.equal(mapWithEmptyKeys);
|
|
});
|
|
var foundKeys = [];
|
|
mapWithEmptyKeys.forEach(function (value, key, entireMap) {
|
|
expect(entireMap.get(key)).to.equal(value);
|
|
foundKeys.push(key);
|
|
});
|
|
expect(foundKeys).to.be.theSameSet(expectedKeys);
|
|
});
|
|
|
|
it('should support the thisArg', function () {
|
|
var context = function () {};
|
|
mapToIterate.forEach(function () {
|
|
expect(this).to.equal(context);
|
|
}, context);
|
|
});
|
|
|
|
it('should have a length of 1', function () {
|
|
expect(Map.prototype.forEach.length).to.equal(1);
|
|
});
|
|
|
|
it('should not revisit modified keys', function () {
|
|
var hasModifiedA = false;
|
|
mapToIterate.forEach(function (value, key) {
|
|
if (!hasModifiedA && key === 'a') {
|
|
expect(mapToIterate.set('a', 4)).to.equal(mapToIterate);
|
|
hasModifiedA = true;
|
|
} else {
|
|
expect(key).not.to.equal('a');
|
|
}
|
|
});
|
|
});
|
|
|
|
it('returns the map from #set() for chaining', function () {
|
|
expect(mapToIterate.set({}, {})).to.equal(mapToIterate);
|
|
expect(mapToIterate.set(42, {})).to.equal(mapToIterate);
|
|
expect(mapToIterate.set(0, {})).to.equal(mapToIterate);
|
|
expect(mapToIterate.set(NaN, {})).to.equal(mapToIterate);
|
|
expect(mapToIterate.set(-0, {})).to.equal(mapToIterate);
|
|
});
|
|
|
|
it('visits keys added in the iterator', function () {
|
|
var hasAdded = false;
|
|
var hasFoundD = false;
|
|
mapToIterate.forEach(function (value, key) {
|
|
if (!hasAdded) {
|
|
mapToIterate.set('d', 5);
|
|
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', function () {
|
|
var hasSeenFour = false;
|
|
var mapToMutate = new Map();
|
|
mapToMutate.set('0', 42);
|
|
mapToMutate.forEach(function (value, key) {
|
|
if (key === '0') {
|
|
expect(mapToMutate['delete']('0')).to.equal(true);
|
|
mapToMutate.set('4', 'a value');
|
|
} 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;
|
|
mapToIterate.forEach(function (value, key) {
|
|
if (key === 'c') {
|
|
hasVisitedC = true;
|
|
}
|
|
if (!hasVisitedC && !hasDeletedC) {
|
|
hasDeletedC = mapToIterate['delete']('c');
|
|
expect(hasDeletedC).to.equal(true);
|
|
}
|
|
});
|
|
expect(hasVisitedC).to.equal(false);
|
|
});
|
|
|
|
it('should work after deletion of the current key', function () {
|
|
var expectedMap = {
|
|
a: 1,
|
|
b: 2,
|
|
c: 3
|
|
};
|
|
var foundMap = {};
|
|
mapToIterate.forEach(function (value, key) {
|
|
foundMap[key] = value;
|
|
expect(mapToIterate['delete'](key)).to.equal(true);
|
|
});
|
|
expect(foundMap).to.eql(expectedMap);
|
|
});
|
|
|
|
it('should convert key -0 to +0', function () {
|
|
var zeroMap = new Map();
|
|
var result = [];
|
|
zeroMap.set(-0, 'a');
|
|
zeroMap.forEach(function (value, key) {
|
|
result.push(String(1 / key) + ' ' + value);
|
|
});
|
|
zeroMap.set(1, 'b');
|
|
zeroMap.set(0, 'c'); // shouldn't cause reordering
|
|
zeroMap.forEach(function (value, key) {
|
|
result.push(String(1 / key) + ' ' + value);
|
|
});
|
|
expect(result.join(', ')).to.equal(
|
|
'Infinity a, Infinity c, 1 b'
|
|
);
|
|
});
|
|
});
|
|
|
|
it('should preserve insertion order', function () {
|
|
var convertToPairs = function (item) { return [item, true]; };
|
|
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) {
|
|
var entries = array.map(convertToPairs);
|
|
expect(new Map(entries)).to.have.entries(entries);
|
|
});
|
|
});
|
|
|
|
it('map iteration', function () {
|
|
var map = new Map();
|
|
map.set('a', 1);
|
|
map.set('b', 2);
|
|
map.set('c', 3);
|
|
map.set('d', 4);
|
|
|
|
var keys = [];
|
|
var iterator = map.keys();
|
|
keys.push(iterator.next().value);
|
|
expect(map['delete']('a')).to.equal(true);
|
|
expect(map['delete']('b')).to.equal(true);
|
|
expect(map['delete']('c')).to.equal(true);
|
|
map.set('e');
|
|
keys.push(iterator.next().value);
|
|
keys.push(iterator.next().value);
|
|
|
|
expect(iterator.next().done).to.equal(true);
|
|
map.set('f');
|
|
expect(iterator.next().done).to.equal(true);
|
|
expect(keys).to.eql(['a', 'd', 'e']);
|
|
});
|
|
});
|