UNPKG

21 kBJavaScriptView Raw
1// Big thanks to V8 folks for test ideas.
2// v8/test/mjsunit/harmony/collections.js
3
4var Assertion = expect().constructor;
5Assertion.addMethod('theSameSet', function (otherArray) {
6 var array = this._obj;
7
8 expect(Array.isArray(array)).to.equal(true);
9 expect(Array.isArray(otherArray)).to.equal(true);
10
11 var diff = array.filter(function (value) {
12 return otherArray.every(function (otherValue) {
13 var areBothNaN = typeof value === 'number' && typeof otherValue === 'number' && value !== value && otherValue !== otherValue;
14 return !areBothNaN && value !== otherValue;
15 });
16 });
17
18 this.assert(
19 diff.length === 0,
20 'expected #{this} to be equal to #{exp} (as sets, i.e. no order)',
21 array,
22 otherArray
23 );
24});
25
26Assertion.addMethod('entries', function (expected) {
27 var collection = this._obj;
28
29 expect(Array.isArray(expected)).to.equal(true);
30 var expectedEntries = expected.slice();
31
32 var iterator = collection.entries();
33 var result;
34 do {
35 result = iterator.next();
36 expect(result.value).to.be.eql(expectedEntries.shift());
37 } while (!result.done);
38});
39
40describe('Map', function () {
41 var functionsHaveNames = (function foo() {}).name === 'foo';
42 var ifFunctionsHaveNamesIt = functionsHaveNames ? it : xit;
43 var ifShimIt = (typeof process !== 'undefined' && process.env.NO_ES6_SHIM) ? it.skip : it;
44 var ifGetPrototypeOfIt = Object.getPrototypeOf ? it : xit;
45
46 var range = function range(from, to) {
47 var result = [];
48 for (var value = from; value < to; value++) {
49 result.push(value);
50 }
51 return result;
52 };
53
54 var prototypePropIsEnumerable = Object.prototype.propertyIsEnumerable.call(function () {}, 'prototype');
55 var expectNotEnumerable = function (object) {
56 if (prototypePropIsEnumerable && typeof object === 'function') {
57 expect(Object.keys(object)).to.eql(['prototype']);
58 } else {
59 expect(Object.keys(object)).to.eql([]);
60 }
61 };
62
63 var Sym = typeof Symbol === 'undefined' ? {} : Symbol;
64 var isSymbol = function (sym) {
65 return typeof Sym === 'function' && typeof sym === 'symbol';
66 };
67 var ifSymbolIteratorIt = isSymbol(Sym.iterator) ? it : xit;
68
69 var testMapping = function (map, key, value) {
70 expect(map.has(key)).to.equal(false);
71 expect(map.get(key)).to.equal(undefined);
72 expect(map.set(key, value)).to.equal(map);
73 expect(map.get(key)).to.equal(value);
74 expect(map.has(key)).to.equal(true);
75 };
76
77 if (typeof Map === 'undefined') {
78 return it('exists', function () {
79 expect(typeof Map).to.equal('function');
80 });
81 }
82
83 var map;
84 beforeEach(function () {
85 map = new Map();
86 });
87
88 afterEach(function () {
89 map = null;
90 });
91
92 ifShimIt('is on the exported object', function () {
93 var exported = require('../');
94 expect(exported.Map).to.equal(Map);
95 });
96
97 it('should exist in global namespace', function () {
98 expect(typeof Map).to.equal('function');
99 });
100
101 it('should have the right arity', function () {
102 expect(Map).to.have.property('length', 0);
103 });
104
105 it('should has valid getter and setter calls', function () {
106 ['get', 'set', 'has', 'delete'].forEach(function (method) {
107 expect(function () {
108 map[method]({});
109 }).to.not['throw']();
110 });
111 });
112
113 it('should accept an iterable as argument', function () {
114 testMapping(map, 'a', 'b');
115 testMapping(map, 'c', 'd');
116 var map2;
117 expect(function () { map2 = new Map(map); }).not.to['throw'](Error);
118 expect(map2).to.be.an.instanceOf(Map);
119 expect(map2.has('a')).to.equal(true);
120 expect(map2.has('c')).to.equal(true);
121 expect(map2).to.have.entries([['a', 'b'], ['c', 'd']]);
122 });
123
124 it('should throw with iterables that return primitives', function () {
125 expect(function () { return new Map('123'); }).to['throw'](TypeError);
126 expect(function () { return new Map([1, 2, 3]); }).to['throw'](TypeError);
127 expect(function () { return new Map(['1', '2', '3']); }).to['throw'](TypeError);
128 expect(function () { return new Map([true]); }).to['throw'](TypeError);
129 });
130
131 it('should not be callable without "new"', function () {
132 expect(Map).to['throw'](TypeError);
133 });
134
135 it('should be subclassable', function () {
136 if (!Object.setPrototypeOf) { return; } // skip test if on IE < 11
137 var MyMap = function MyMap() {
138 var testMap = new Map([['a', 'b']]);
139 Object.setPrototypeOf(testMap, MyMap.prototype);
140 return testMap;
141 };
142 Object.setPrototypeOf(MyMap, Map);
143 MyMap.prototype = Object.create(Map.prototype, {
144 constructor: { value: MyMap }
145 });
146
147 var myMap = new MyMap();
148 testMapping(myMap, 'c', 'd');
149 expect(myMap).to.have.entries([['a', 'b'], ['c', 'd']]);
150 });
151
152 it('uses SameValueZero even on a Map of size > 4', function () {
153 // Chrome 38-42, node 0.11/0.12, iojs 1/2 have a bug when the Map has a size > 4
154 var firstFour = [[1, 0], [2, 0], [3, 0], [4, 0]];
155 var fourMap = new Map(firstFour);
156 expect(fourMap.size).to.equal(4);
157 expect(fourMap.has(-0)).to.equal(false);
158 expect(fourMap.has(0)).to.equal(false);
159
160 fourMap.set(-0, fourMap);
161
162 expect(fourMap.has(0)).to.equal(true);
163 expect(fourMap.has(-0)).to.equal(true);
164 });
165
166 it('treats positive and negative zero the same', function () {
167 var value1 = {};
168 var value2 = {};
169 testMapping(map, +0, value1);
170 expect(map.has(-0)).to.equal(true);
171 expect(map.get(-0)).to.equal(value1);
172 expect(map.set(-0, value2)).to.equal(map);
173 expect(map.get(-0)).to.equal(value2);
174 expect(map.get(+0)).to.equal(value2);
175 });
176
177 it('should map values correctly', function () {
178 // Run this test twice, one with the "fast" implementation (which only
179 // allows string and numeric keys) and once with the "slow" impl.
180 [true, false].forEach(function (slowkeys) {
181 map = new Map();
182
183 range(1, 20).forEach(function (number) {
184 if (slowkeys) { testMapping(map, number, {}); }
185 testMapping(map, number / 100, {});
186 testMapping(map, 'key-' + number, {});
187 testMapping(map, String(number), {});
188 if (slowkeys) { testMapping(map, Object(String(number)), {}); }
189 });
190
191 var testkeys = [Infinity, -Infinity, NaN];
192 if (slowkeys) {
193 testkeys.push(true, false, null, undefined);
194 }
195 testkeys.forEach(function (key) {
196 testMapping(map, key, {});
197 testMapping(map, String(key), {});
198 });
199 testMapping(map, '', {});
200
201 // verify that properties of Object don't peek through.
202 [
203 'hasOwnProperty',
204 'constructor',
205 'toString',
206 'isPrototypeOf',
207 '__proto__',
208 '__parent__',
209 '__count__'
210 ].forEach(function (key) {
211 testMapping(map, key, {});
212 });
213 });
214 });
215
216 it('should map empty values correctly', function () {
217 testMapping(map, {}, true);
218 testMapping(map, null, true);
219 testMapping(map, undefined, true);
220 testMapping(map, '', true);
221 testMapping(map, NaN, true);
222 testMapping(map, 0, true);
223 });
224
225 it('should has correct querying behavior', function () {
226 var key = {};
227 testMapping(map, key, 'to-be-present');
228 expect(map.has(key)).to.equal(true);
229 expect(map.has({})).to.equal(false);
230 expect(map.set(key, void 0)).to.equal(map);
231 expect(map.get(key)).to.equal(undefined);
232 expect(map.has(key)).to.equal(true);
233 expect(map.has({})).to.equal(false);
234 });
235
236 it('should allow NaN values as keys', function () {
237 expect(map.has(NaN)).to.equal(false);
238 expect(map.has(NaN + 1)).to.equal(false);
239 expect(map.has(23)).to.equal(false);
240 expect(map.set(NaN, 'value')).to.equal(map);
241 expect(map.has(NaN)).to.equal(true);
242 expect(map.has(NaN + 1)).to.equal(true);
243 expect(map.has(23)).to.equal(false);
244 });
245
246 it('should not have [[Enumerable]] props', function () {
247 expectNotEnumerable(Map);
248 expectNotEnumerable(Map.prototype);
249 expectNotEnumerable(new Map());
250 });
251
252 it('should not have an own constructor', function () {
253 var m = new Map();
254 expect(m).not.to.haveOwnPropertyDescriptor('constructor');
255 expect(m.constructor).to.equal(Map);
256 });
257
258 it('should allow common ecmascript idioms', function () {
259 expect(map).to.be.an.instanceOf(Map);
260 expect(typeof Map.prototype.get).to.equal('function');
261 expect(typeof Map.prototype.set).to.equal('function');
262 expect(typeof Map.prototype.has).to.equal('function');
263 expect(typeof Map.prototype['delete']).to.equal('function');
264 });
265
266 it('should have a unique constructor', function () {
267 expect(Map.prototype).to.not.equal(Object.prototype);
268 });
269
270 describe('#clear()', function () {
271 ifFunctionsHaveNamesIt('has the right name', function () {
272 expect(Map.prototype.clear).to.have.property('name', 'clear');
273 });
274
275 it('is not enumerable', function () {
276 expect(Map.prototype).ownPropertyDescriptor('clear').to.have.property('enumerable', false);
277 });
278
279 it('has the right arity', function () {
280 expect(Map.prototype.clear).to.have.property('length', 0);
281 });
282
283 it('should have #clear method', function () {
284 expect(map.set(1, 2)).to.equal(map);
285 expect(map.set(5, 2)).to.equal(map);
286 expect(map.size).to.equal(2);
287 expect(map.has(5)).to.equal(true);
288 map.clear();
289 expect(map.size).to.equal(0);
290 expect(map.has(5)).to.equal(false);
291 });
292 });
293
294 describe('#keys()', function () {
295 if (!Object.prototype.hasOwnProperty.call(Map.prototype, 'keys')) {
296 return it('exists', function () {
297 expect(Map.prototype).to.have.property('keys');
298 });
299 }
300
301 ifFunctionsHaveNamesIt('has the right name', function () {
302 expect(Map.prototype.keys).to.have.property('name', 'keys');
303 });
304
305 it('is not enumerable', function () {
306 expect(Map.prototype).ownPropertyDescriptor('keys').to.have.property('enumerable', false);
307 });
308
309 it('has the right arity', function () {
310 expect(Map.prototype.keys).to.have.property('length', 0);
311 });
312 });
313
314 describe('#values()', function () {
315 if (!Object.prototype.hasOwnProperty.call(Map.prototype, 'values')) {
316 return it('exists', function () {
317 expect(Map.prototype).to.have.property('values');
318 });
319 }
320
321 ifFunctionsHaveNamesIt('has the right name', function () {
322 expect(Map.prototype.values).to.have.property('name', 'values');
323 });
324
325 it('is not enumerable', function () {
326 expect(Map.prototype).ownPropertyDescriptor('values').to.have.property('enumerable', false);
327 });
328
329 it('has the right arity', function () {
330 expect(Map.prototype.values).to.have.property('length', 0);
331 });
332 });
333
334 describe('#entries()', function () {
335 if (!Object.prototype.hasOwnProperty.call(Map.prototype, 'entries')) {
336 return it('exists', function () {
337 expect(Map.prototype).to.have.property('entries');
338 });
339 }
340
341 ifFunctionsHaveNamesIt('has the right name', function () {
342 expect(Map.prototype.entries).to.have.property('name', 'entries');
343 });
344
345 it('is not enumerable', function () {
346 expect(Map.prototype).ownPropertyDescriptor('entries').to.have.property('enumerable', false);
347 });
348
349 it('has the right arity', function () {
350 expect(Map.prototype.entries).to.have.property('length', 0);
351 });
352
353 it('throws when called on a non-Map', function () {
354 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$|Map.prototype.entries requires that \|this\| be Map+$|is not an object \(evaluating 'Map.prototype.entries.call\(nonMap\)'\)|Map operation called on non-Map object/;
355 var nonMaps = [true, false, 'abc', NaN, new Set([1, 2]), { a: true }, [1], Object('abc'), Object(NaN)];
356 nonMaps.forEach(function (nonMap) {
357 expect(function () { return Map.prototype.entries.call(nonMap); }).to['throw'](TypeError, expectedMessage);
358 });
359 });
360 });
361
362 describe('#size', function () {
363 it('throws TypeError when accessed directly', function () {
364 // see https://github.com/paulmillr/es6-shim/issues/176
365 expect(function () { return Map.prototype.size; }).to['throw'](TypeError);
366 expect(function () { return Map.prototype.size; }).to['throw'](TypeError);
367 });
368
369 it('is an accessor function on the prototype', function () {
370 expect(Map.prototype).ownPropertyDescriptor('size').to.have.property('get');
371 expect(typeof Object.getOwnPropertyDescriptor(Map.prototype, 'size').get).to.equal('function');
372 expect(new Map()).not.to.haveOwnPropertyDescriptor('size');
373 });
374 });
375
376 it('should return false when deleting a nonexistent key', function () {
377 expect(map.has('a')).to.equal(false);
378 expect(map['delete']('a')).to.equal(false);
379 });
380
381 it('should have keys, values and size props', function () {
382 expect(map.set('a', 1)).to.equal(map);
383 expect(map.set('b', 2)).to.equal(map);
384 expect(map.set('c', 3)).to.equal(map);
385 expect(typeof map.keys).to.equal('function');
386 expect(typeof map.values).to.equal('function');
387 expect(map.size).to.equal(3);
388 expect(map['delete']('a')).to.equal(true);
389 expect(map.size).to.equal(2);
390 });
391
392 it('should have an iterator that works with Array.from', function () {
393 expect(Array).to.have.property('from');
394
395 expect(map.set('a', 1)).to.equal(map);
396 expect(map.set('b', NaN)).to.equal(map);
397 expect(map.set('c', false)).to.equal(map);
398 expect(Array.from(map)).to.eql([['a', 1], ['b', NaN], ['c', false]]);
399 expect(Array.from(map.keys())).to.eql(['a', 'b', 'c']);
400 expect(Array.from(map.values())).to.eql([1, NaN, false]);
401 expect(map).to.have.entries(Array.from(map.entries()));
402 });
403
404 ifSymbolIteratorIt('has the right default iteration function', function () {
405 // fixed in Webkit https://bugs.webkit.org/show_bug.cgi?id=143838
406 expect(Map.prototype).to.have.property(Sym.iterator, Map.prototype.entries);
407 });
408
409 describe('#forEach', function () {
410 var mapToIterate;
411
412 beforeEach(function () {
413 mapToIterate = new Map();
414 expect(mapToIterate.set('a', 1)).to.equal(mapToIterate);
415 expect(mapToIterate.set('b', 2)).to.equal(mapToIterate);
416 expect(mapToIterate.set('c', 3)).to.equal(mapToIterate);
417 });
418
419 afterEach(function () {
420 mapToIterate = null;
421 });
422
423 ifFunctionsHaveNamesIt('has the right name', function () {
424 expect(Map.prototype.forEach).to.have.property('name', 'forEach');
425 });
426
427 it('is not enumerable', function () {
428 expect(Map.prototype).ownPropertyDescriptor('forEach').to.have.property('enumerable', false);
429 });
430
431 it('has the right arity', function () {
432 expect(Map.prototype.forEach).to.have.property('length', 1);
433 });
434
435 it('should be iterable via forEach', function () {
436 var expectedMap = {
437 a: 1,
438 b: 2,
439 c: 3
440 };
441 var foundMap = {};
442 mapToIterate.forEach(function (value, key, entireMap) {
443 expect(entireMap).to.equal(mapToIterate);
444 foundMap[key] = value;
445 });
446 expect(foundMap).to.eql(expectedMap);
447 });
448
449 it('should iterate over empty keys', function () {
450 var mapWithEmptyKeys = new Map();
451 var expectedKeys = [{}, null, undefined, '', NaN, 0];
452 expectedKeys.forEach(function (key) {
453 expect(mapWithEmptyKeys.set(key, true)).to.equal(mapWithEmptyKeys);
454 });
455 var foundKeys = [];
456 mapWithEmptyKeys.forEach(function (value, key, entireMap) {
457 expect(entireMap.get(key)).to.equal(value);
458 foundKeys.push(key);
459 });
460 expect(foundKeys).to.be.theSameSet(expectedKeys);
461 });
462
463 it('should support the thisArg', function () {
464 var context = function () {};
465 mapToIterate.forEach(function () {
466 expect(this).to.equal(context);
467 }, context);
468 });
469
470 it('should have a length of 1', function () {
471 expect(Map.prototype.forEach.length).to.equal(1);
472 });
473
474 it('should not revisit modified keys', function () {
475 var hasModifiedA = false;
476 mapToIterate.forEach(function (value, key) {
477 if (!hasModifiedA && key === 'a') {
478 expect(mapToIterate.set('a', 4)).to.equal(mapToIterate);
479 hasModifiedA = true;
480 } else {
481 expect(key).not.to.equal('a');
482 }
483 });
484 });
485
486 it('returns the map from #set() for chaining', function () {
487 expect(mapToIterate.set({}, {})).to.equal(mapToIterate);
488 expect(mapToIterate.set(42, {})).to.equal(mapToIterate);
489 expect(mapToIterate.set(0, {})).to.equal(mapToIterate);
490 expect(mapToIterate.set(NaN, {})).to.equal(mapToIterate);
491 expect(mapToIterate.set(-0, {})).to.equal(mapToIterate);
492 });
493
494 it('visits keys added in the iterator', function () {
495 var hasAdded = false;
496 var hasFoundD = false;
497 mapToIterate.forEach(function (value, key) {
498 if (!hasAdded) {
499 mapToIterate.set('d', 5);
500 hasAdded = true;
501 } else if (key === 'd') {
502 hasFoundD = true;
503 }
504 });
505 expect(hasFoundD).to.equal(true);
506 });
507
508 it('visits keys added in the iterator when there is a deletion', function () {
509 var hasSeenFour = false;
510 var mapToMutate = new Map();
511 mapToMutate.set('0', 42);
512 mapToMutate.forEach(function (value, key) {
513 if (key === '0') {
514 expect(mapToMutate['delete']('0')).to.equal(true);
515 mapToMutate.set('4', 'a value');
516 } else if (key === '4') {
517 hasSeenFour = true;
518 }
519 });
520 expect(hasSeenFour).to.equal(true);
521 });
522
523 it('does not visit keys deleted before a visit', function () {
524 var hasVisitedC = false;
525 var hasDeletedC = false;
526 mapToIterate.forEach(function (value, key) {
527 if (key === 'c') {
528 hasVisitedC = true;
529 }
530 if (!hasVisitedC && !hasDeletedC) {
531 hasDeletedC = mapToIterate['delete']('c');
532 expect(hasDeletedC).to.equal(true);
533 }
534 });
535 expect(hasVisitedC).to.equal(false);
536 });
537
538 it('should work after deletion of the current key', function () {
539 var expectedMap = {
540 a: 1,
541 b: 2,
542 c: 3
543 };
544 var foundMap = {};
545 mapToIterate.forEach(function (value, key) {
546 foundMap[key] = value;
547 expect(mapToIterate['delete'](key)).to.equal(true);
548 });
549 expect(foundMap).to.eql(expectedMap);
550 });
551
552 it('should convert key -0 to +0', function () {
553 var zeroMap = new Map();
554 var result = [];
555 zeroMap.set(-0, 'a');
556 zeroMap.forEach(function (value, key) {
557 result.push(String(1 / key) + ' ' + value);
558 });
559 zeroMap.set(1, 'b');
560 zeroMap.set(0, 'c'); // shouldn't cause reordering
561 zeroMap.forEach(function (value, key) {
562 result.push(String(1 / key) + ' ' + value);
563 });
564 expect(result.join(', ')).to.equal('Infinity a, Infinity c, 1 b');
565 });
566 });
567
568 it('should preserve insertion order', function () {
569 var convertToPairs = function (item) { return [item, true]; };
570 var arr1 = ['d', 'a', 'b'];
571 var arr2 = [3, 2, 'z', 'a', 1];
572 var arr3 = [3, 2, 'z', {}, 'a', 1];
573
574 [arr1, arr2, arr3].forEach(function (array) {
575 var entries = array.map(convertToPairs);
576 expect(new Map(entries)).to.have.entries(entries);
577 });
578 });
579
580 it('map iteration', function () {
581 var map = new Map();
582 map.set('a', 1);
583 map.set('b', 2);
584 map.set('c', 3);
585 map.set('d', 4);
586
587 var keys = [];
588 var iterator = map.keys();
589 keys.push(iterator.next().value);
590 expect(map['delete']('a')).to.equal(true);
591 expect(map['delete']('b')).to.equal(true);
592 expect(map['delete']('c')).to.equal(true);
593 map.set('e');
594 keys.push(iterator.next().value);
595 keys.push(iterator.next().value);
596
597 expect(iterator.next().done).to.equal(true);
598 map.set('f');
599 expect(iterator.next().done).to.equal(true);
600 expect(keys).to.eql(['a', 'd', 'e']);
601 });
602
603 ifGetPrototypeOfIt('MapIterator identification test prototype inequality', function () {
604 var mapEntriesProto = Object.getPrototypeOf(new Map().entries());
605 var setEntriesProto = Object.getPrototypeOf(new Set().entries());
606 expect(mapEntriesProto).to.not.equal(setEntriesProto);
607 });
608
609 it('MapIterator identification', function () {
610 var fnMapValues = Map.prototype.values;
611 var mapSentinel = new Map([[1, 'MapSentinel']]);
612 var testMap = new Map();
613 var testMapValues = testMap.values();
614 expect(testMapValues.next.call(fnMapValues.call(mapSentinel)).value).to.equal('MapSentinel');
615
616 var testSet = new Set();
617 var testSetValues = testSet.values();
618 expect(function () {
619 return testSetValues.next.call(fnMapValues.call(mapSentinel)).value;
620 }).to['throw'](TypeError);
621 });
622});