1import "jasmine";
2import {enumerate, filter, map, reduce} from './utils';
3
4describe('map', () => {
5 it('should map over an array', () => {
6 expect(Array.from(map([1, 3, 5], (x) => 2 * x))).toEqual([2, 6, 10]);
7 });
8
9 it('should call the function lazily', () => {
10 const spy = jasmine.createSpy('mapper').and.callFake((x: number) => 2 * x);
11
12 const [generatorSpy, input] = iterableSpy([1, 3]);
13 const iterable = map(input, spy);
14 const iterator = iterable[Symbol.iterator]();
15 expect(generatorSpy).not.toHaveBeenCalled();
16 expect(spy).not.toHaveBeenCalled();
17
18 let next = iterator.next();
19 expect(next.value).toBe(2);
20 expect(spy).toHaveBeenCalledTimes(1);
21 expect(spy).toHaveBeenCalledWith(1);
22 expect(generatorSpy).toHaveBeenCalledTimes(1);
23
24 next = iterator.next();
25 expect(next.value).toBe(6);
26 expect(spy).toHaveBeenCalledTimes(2);
27 expect(spy).toHaveBeenCalledWith(3);
28 expect(generatorSpy).toHaveBeenCalledTimes(2);
29
30 next = iterator.next();
31 expect(next.done).toBe(true);
32 expect(spy).toHaveBeenCalledTimes(2);
33 expect(generatorSpy).toHaveBeenCalledTimes(3);
34 });
35
36 it('should accept non-array iterables', () => {
37 function* generator(): Iterable<number> {
38 yield 1;
39 yield 3;
40 }
41 expect(Array.from(map(generator(), (x) => x * 2))).toEqual([2, 6]);
42 });
43
44 it('should do nothing with empty input', () => {
45 expect(Array.from(map([], (x) => x))).toEqual([]);
46 });
47});
48
49describe('reduce', () => {
50 it('should reduce non-array iterators', () => {
51 function* generator(): Iterable<number> {
52 yield 1;
53 yield 2;
54 }
55 expect(reduce(generator(), (acc, x) => acc + x, 0)).toBe(3);
56 });
57});
58
59describe('filter', () => {
60 it('should accept and produce iterables', () => {
61 const [inputSpy, input] = iterableSpy([1, 2, 3, 4]);
62
63 const f = jasmine.createSpy('f').and.callFake((x: number) => x % 2 === 0);
64 const iterable = filter(input, f);
65 const iterator = iterable[Symbol.iterator]();
66
67 expect(f).not.toHaveBeenCalled();
68 let value = iterator.next();
69 expect(value.value).toBe(2);
70 expect(f).toHaveBeenCalledTimes(2);
71 expect(inputSpy).toHaveBeenCalledTimes(2);
72
73 value = iterator.next();
74 expect(value.value).toBe(4);
75 expect(f).toHaveBeenCalledTimes(4);
76 expect(inputSpy).toHaveBeenCalledTimes(4);
77
78 value = iterator.next();
79 expect(value.done).toBe(true);
80 expect(f).toHaveBeenCalledTimes(4);
81 });
82});
83
84describe('enumerate', () => {
85 it('should count up', () => {
86 expect(Array.from(enumerate(['hello', 'world']))).toEqual([
87 [0, 'hello'], [1, 'world'],
88 ]);
89 });
90
91 it('should accept and produce iterables', () => {
92 const [inputSpy, input] = iterableSpy(['hello', 'world']);
93
94 const iterable = enumerate(input);
95 const iterator = iterable[Symbol.iterator]();
96
97 expect(inputSpy).not.toHaveBeenCalled();
98
99 let value = iterator.next();
100 expect(value.value).toEqual([0, 'hello']);
101 expect(inputSpy).toHaveBeenCalledTimes(1);
102
103 value = iterator.next();
104 expect(value.value).toEqual([1, 'world']);
105 expect(inputSpy).toHaveBeenCalledTimes(2);
106
107 value = iterator.next();
108 expect(value.done).toBe(true);
109 expect(inputSpy).toHaveBeenCalledTimes(3);
110 });
111});
112
113// Given an array, returns an iterable that yields the values one at a time,
114// and also a Spy that lets you observe the usage of that iterable.
115function iterableSpy<T>(output: T[]): [jasmine.Spy, Iterable<T>] {
116 const iterator = {
117 next(): IteratorResult<T> {
118 if (output.length > 0) {
119 return {value: output.shift()!, done: false};
120 } else {
121 // IteratorResult<T> is incorrect for finished iterators, apparently.
122 return {done: true} as IteratorResult<T>;
123 }
124 },
125 };
126
127 const iterable = {
128 [Symbol.iterator]() {
129 return iterator;
130 },
131 };
132
133 const spy = spyOn(iterator, 'next').and.callThrough();
134 return [spy, iterable];
135}
View as plain text