diff --git a/packages/tao/src/utils.js b/packages/tao/src/utils.js index b416313..9a41120 100644 --- a/packages/tao/src/utils.js +++ b/packages/tao/src/utils.js @@ -4,18 +4,25 @@ export function isIterable(obj) { if (obj == null) { return false; } + if (typeof obj === 'string') { + return false; + } return typeof obj[Symbol.iterator] === 'function'; } // needed a convenience function for this export function concatIterables(...iterables) { - const rv = []; + let rv = []; if (iterables.length) { - iterables.forEach(list => { - if (list.length) { - list.forEach(item => rv.concat(item)); + for (let list of iterables) { + let iterator = list[Symbol.iterator](); + if (list.values && typeof list.values === 'function') { + iterator = list.values(); + } + for (let item of iterator) { + rv = rv.concat(item); } - }); + } } return rv; } diff --git a/packages/tao/src/utils.spec.js b/packages/tao/src/utils.spec.js new file mode 100644 index 0000000..6b509b8 --- /dev/null +++ b/packages/tao/src/utils.spec.js @@ -0,0 +1,191 @@ +import { isIterable, concatIterables } from './utils'; + +describe('isIterable tests whether a value can be iterated as a collection', () => { + it('should return false for null', () => { + // Assemble + const expected = false; + const toTest = null; + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); + + it('should return false for undefined', () => { + // Assemble + const expected = false; + const toTest = undefined; + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); + + it('should return false for string', () => { + // Assemble + const expected = false; + const toTest = 'string to test'; + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); + + it('should return false for numbers', () => { + // Assemble + const expected = false; + const toTest = 8.97; + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); + + it('should return false for boolean', () => { + // Assemble + const expected = false; + const toTest = true; + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); + + it('should return false for Object', () => { + // Assemble + const expected = false; + const toTest = {}; + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); + + it('should return false for Symbol', () => { + // Assemble + const expected = false; + const toTest = Symbol('mang'); + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); + + it('should return false for Date', () => { + // Assemble + const expected = false; + const toTest = Date.now(); + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); + + it('should return true for Array', () => { + // Assemble + const expected = true; + const toTest = []; + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); + + it('should return true for typed Arrays', () => { + // Assemble + const expected = true; + const toTest = new Int16Array(); + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); + + it('should return true for Maps', () => { + // Assemble + const expected = true; + const toTest = new Map(); + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); + + it('should return false for WeakMaps', () => { + // Assemble + const expected = false; + const toTest = new WeakMap(); + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); + + it('should return true for Sets', () => { + // Assemble + const expected = true; + const toTest = new Set(); + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); + + it('should return false for WeakSets', () => { + // Assemble + const expected = false; + const toTest = new WeakSet(); + // Act + const actual = isIterable(toTest); + // Assert + expect(actual).toBe(expected); + }); +}); + +describe('concatIterables returns an Iterable made of concatenating all iterables passed', () => { + it('should return empty iterable if passed nothing', () => { + expect(concatIterables()).toEqual([]); + }); + + it('should concatenate several Arrays', () => { + // Assemble + const first = [1, 2]; + const second = [3, 4, 5]; + const third = [6, 7, 8]; + const expected = [1, 2, 3, 4, 5, 6, 7, 8]; + // Act + const actual = concatIterables(first, second, third); + // Assert + // expect(actual.length).toBe(expected.length); + expect(actual).toEqual(expected); + }); + + it('should ignore empty collections', () => { + // Assemble + const first = []; + const second = [1, 2, 3, 4, 5]; + const third = []; + const expected = second; + // Act + const actual = concatIterables(first, second, third); + // Assert + // expect(actual.length).toBe(expected.length); + expect(actual).toEqual(expected); + }); + + it('should concatenate different collection types', () => { + // Assemble + const first = [1, 2]; + const second = new Set([3, 4, 5]); + const third = new Map(); + third.set('six', 6); + third.set('seven', 7); + third.set('eight', 8); + const expected = [1, 2, 3, 4, 5, 6, 7, 8]; + // Act + const actual = concatIterables(first, second, third); + // Assert + // expect(actual.length).toBe(expected.length); + expect(actual).toEqual(expected); + }); +});