extra-iterator 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3,6 +3,33 @@ interface ArrayIsh<T> {
3
3
  length: number;
4
4
  }
5
5
  export type ExtraIteratorSource<T> = Iterator<T, any, any> | Iterable<T, any, any> | ArrayIsh<T>;
6
+ /**
7
+ * An extended iterator class that provides additional chainable utility methods for working with iterables.
8
+ *
9
+ * @template T The type of values yielded by this iterator.
10
+ *
11
+ * @example
12
+ * // Creating an iterator from an array
13
+ * const iter = ExtraIterator.from([1, 2, 3, 4, 5]);
14
+ *
15
+ * @example
16
+ * // Creating a sequence of ancestors of a given element
17
+ * ExtraIterator.from(function*() {
18
+ *
19
+ * }()).toArray();
20
+ * ExtraIterator.from([1, 2, 3, 4, 5])
21
+ * .filter(n => n % 2 === 0)
22
+ * .map(n => n * 2)
23
+ * .toArray()
24
+ * // returns [4, 8]
25
+ *
26
+ * @example
27
+ * // Using static factory methods
28
+ * ExtraIterator.range(1, 5)
29
+ * .map(n => n * n)
30
+ * .toArray()
31
+ * // returns [1, 4, 9, 16]
32
+ */
6
33
  export declare class ExtraIterator<T> extends Iterator<T, any, any> {
7
34
  static from<T>(source: ExtraIteratorSource<T>): ExtraIterator<T>;
8
35
  static zip<A, B>(a: ExtraIteratorSource<A>, b: ExtraIteratorSource<B>): ExtraIterator<[A, B]>;
@@ -25,6 +52,12 @@ export declare class ExtraIterator<T> extends Iterator<T, any, any> {
25
52
  * @example ExtraIterator.empty().toArray() // returns []
26
53
  */
27
54
  static empty<T = any>(): ExtraIterator<T>;
55
+ /**
56
+ * Creates an iterator that yields a single value.
57
+ *
58
+ * @example ExtraIterator.single(42).toArray() // returns [42]
59
+ */
60
+ static single<T>(value: T): ExtraIterator<T>;
28
61
  /**
29
62
  * Creates an iterator that yields incrementing numbers.
30
63
  *
@@ -70,14 +103,30 @@ export declare class ExtraIterator<T> extends Iterator<T, any, any> {
70
103
  */
71
104
  static repeat<T>(value: T): ExtraIterator<T>;
72
105
  /**
73
- * Generates an infinite sequence of cryptographically strong random bytes using `crypto.getRandomValues`. Each
74
- * yielded value is a number in between 0 and 255 (inclusive).
106
+ * Generates an infinite sequence of random numbers between 0 and 1 (exclusive) using `Math.random` or another
107
+ * specified random number generator.
75
108
  *
76
109
  * > ⚠ This iterator is infinite. Use {@link take} method if you want a specific number of values.
77
110
  */
78
- static random({ bufferSize }?: {
79
- bufferSize?: number | undefined;
80
- }): ExtraIterator<number>;
111
+ static random(rng?: () => number): ExtraIterator<number>;
112
+ /**
113
+ * Generates an infinite sequence of cryptographically strong random bytes using `crypto.getRandomValues`, in
114
+ * chunks of `chunkSize` bytes.
115
+ *
116
+ * By default, this method reuses the same `ArrayBuffer` instance for each chunk, refilling it with new random
117
+ * values at each iteration, so you should not keep references to the yielded buffers. Set `reuseBuffer` to `false`
118
+ * if you want this method to yield copies of the buffer instead.
119
+ *
120
+ * If you want a flat sequence of individual byte values instead of chunks, you can chain the iterator returned by
121
+ * this method with the {@link flatten} method. The resulting iterator will contain interger values from 0 to 255
122
+ * (inclusive).
123
+ *
124
+ * > ⚠ This iterator is infinite. Use {@link take} method if you want a specific number of values.
125
+ */
126
+ static randomBytes({ chunkSize, reuseBuffer }?: {
127
+ chunkSize?: number | undefined;
128
+ reuseBuffer?: boolean | undefined;
129
+ }): ExtraIterator<ArrayBuffer>;
81
130
  private constructor();
82
131
  private source;
83
132
  next(value?: any): IteratorResult<T, any>;
@@ -163,11 +212,11 @@ export declare class ExtraIterator<T> extends Iterator<T, any, any> {
163
212
  * @example
164
213
  *
165
214
  * ExtraIterator.from([4, 5, 6])
166
- * .prependMany([1, 2, 3])
215
+ * .prependAll([1, 2, 3])
167
216
  * .toArray()
168
217
  * // returns [1, 2, 3, 4, 5, 6]
169
218
  */
170
- prependMany<U>(items: Iterable<U>): ExtraIterator<T | U>;
219
+ prependAll<U>(items: Iterable<U>): ExtraIterator<T | U>;
171
220
  /**
172
221
  * Creates a new iterator that invokes the provided callback function over each element of this iterator and yields
173
222
  * the elements for which the callback returns `true`, only for as long as the callback returns `true`.
@@ -213,9 +262,9 @@ export declare class ExtraIterator<T> extends Iterator<T, any, any> {
213
262
  /**
214
263
  * Groups the elements in this iterator into groups of variable size.
215
264
  *
216
- * This method calls the provided predicate function for each pair of adjacent elements in this iterator. It should
217
- * return `true` if the elements should belong to the same group, or `false` if they should belong to different
218
- * groups.
265
+ * This method calls the provided predicate function for each pair of adjacent elements in this iterator. The
266
+ * function should return `true` if the elements should belong to the same group, or `false` if they should belong
267
+ * to different groups.
219
268
  *
220
269
  * The resulting iterator yields arrays of elements that belong to the same group.
221
270
  *
@@ -292,7 +341,8 @@ export declare class ExtraIterator<T> extends Iterator<T, any, any> {
292
341
  */
293
342
  splice(startIndex: number, deleteCount: number, ...newItems: T[]): ExtraIterator<T>;
294
343
  /**
295
- * Returns an iterator the provided element if this iterator is empty; otherwise, it returns this iterator.
344
+ * If this iterator is empty, returns an iterator with the provided element as its only element; otherwise, it
345
+ * returns a copy of this iterator.
296
346
  */
297
347
  defaultIfEmpty(provider: () => T): ExtraIterator<T>;
298
348
  /**
@@ -380,8 +430,8 @@ export declare class ExtraIterator<T> extends Iterator<T, any, any> {
380
430
  *
381
431
  * @example
382
432
  *
383
- * ExtraIterator.from([1, 2, 3]).uniqueness() // returns true
384
- * ExtraIterator.from([1, 2, 3, 1]).uniqueness() // returns false
433
+ * ExtraIterator.from([1, 2, 3]).testUnique() // returns true
434
+ * ExtraIterator.from([1, 2, 3, 1]).testUnique() // returns false
385
435
  */
386
436
  testUnique(mapper?: (value: T) => unknown): boolean;
387
437
  /**
package/dist/index.js CHANGED
@@ -1,8 +1,34 @@
1
+ /**
2
+ * An extended iterator class that provides additional chainable utility methods for working with iterables.
3
+ *
4
+ * @template T The type of values yielded by this iterator.
5
+ *
6
+ * @example
7
+ * // Creating an iterator from an array
8
+ * const iter = ExtraIterator.from([1, 2, 3, 4, 5]);
9
+ *
10
+ * @example
11
+ * // Creating a sequence of ancestors of a given element
12
+ * ExtraIterator.from(function*() {
13
+ *
14
+ * }()).toArray();
15
+ * ExtraIterator.from([1, 2, 3, 4, 5])
16
+ * .filter(n => n % 2 === 0)
17
+ * .map(n => n * 2)
18
+ * .toArray()
19
+ * // returns [4, 8]
20
+ *
21
+ * @example
22
+ * // Using static factory methods
23
+ * ExtraIterator.range(1, 5)
24
+ * .map(n => n * n)
25
+ * .toArray()
26
+ * // returns [1, 4, 9, 16]
27
+ */
1
28
  export class ExtraIterator extends Iterator {
2
29
  // =================================================================================================================
3
30
  // STATIC FUNCTIONS
4
31
  // =================================================================================================================
5
- // TODO Consider using a lib like `make-iterator` to transform things into iterators
6
32
  static from(source) {
7
33
  if (!(Symbol.iterator in source) && 'length' in source) {
8
34
  return new ExtraIterator(function* () {
@@ -29,6 +55,14 @@ export class ExtraIterator extends Iterator {
29
55
  static empty() {
30
56
  return new ExtraIterator([]);
31
57
  }
58
+ /**
59
+ * Creates an iterator that yields a single value.
60
+ *
61
+ * @example ExtraIterator.single(42).toArray() // returns [42]
62
+ */
63
+ static single(value) {
64
+ return new ExtraIterator([value]);
65
+ }
32
66
  /**
33
67
  * Creates an iterator that yields incrementing numbers.
34
68
  *
@@ -98,16 +132,37 @@ export class ExtraIterator extends Iterator {
98
132
  }());
99
133
  }
100
134
  /**
101
- * Generates an infinite sequence of cryptographically strong random bytes using `crypto.getRandomValues`. Each
102
- * yielded value is a number in between 0 and 255 (inclusive).
135
+ * Generates an infinite sequence of random numbers between 0 and 1 (exclusive) using `Math.random` or another
136
+ * specified random number generator.
103
137
  *
104
138
  * > ⚠ This iterator is infinite. Use {@link take} method if you want a specific number of values.
105
139
  */
106
- static random({ bufferSize = 1024 } = {}) {
107
- const buffer = new Uint8Array(bufferSize);
140
+ static random(rng = Math.random) {
141
+ return ExtraIterator.from(function* () {
142
+ while (true) {
143
+ yield rng();
144
+ }
145
+ }());
146
+ }
147
+ /**
148
+ * Generates an infinite sequence of cryptographically strong random bytes using `crypto.getRandomValues`, in
149
+ * chunks of `chunkSize` bytes.
150
+ *
151
+ * By default, this method reuses the same `ArrayBuffer` instance for each chunk, refilling it with new random
152
+ * values at each iteration, so you should not keep references to the yielded buffers. Set `reuseBuffer` to `false`
153
+ * if you want this method to yield copies of the buffer instead.
154
+ *
155
+ * If you want a flat sequence of individual byte values instead of chunks, you can chain the iterator returned by
156
+ * this method with the {@link flatten} method. The resulting iterator will contain interger values from 0 to 255
157
+ * (inclusive).
158
+ *
159
+ * > ⚠ This iterator is infinite. Use {@link take} method if you want a specific number of values.
160
+ */
161
+ static randomBytes({ chunkSize = 1024, reuseBuffer = true } = {}) {
162
+ const bytes = new Uint8Array(chunkSize);
108
163
  return new ExtraIterator(function* () {
109
- globalThis.crypto.getRandomValues(buffer);
110
- yield* new Uint8Array(buffer);
164
+ globalThis.crypto.getRandomValues(bytes);
165
+ yield reuseBuffer ? bytes.buffer : new Uint8Array(bytes).buffer;
111
166
  }())
112
167
  .loop();
113
168
  }
@@ -140,17 +195,19 @@ export class ExtraIterator extends Iterator {
140
195
  take(limit) {
141
196
  return limit >= 0
142
197
  ? ExtraIterator.from(super.take(limit))
143
- : ExtraIterator.from(this.takeLast(-limit));
198
+ : this.takeLast(-limit);
144
199
  }
145
200
  takeLast(count) {
146
- const result = [];
147
- for (let item; item = this.next(), !item.done;) {
148
- result.push(item.value);
149
- if (result.length > count) {
150
- result.shift();
151
- }
201
+ const ringbuffer = new Array(count);
202
+ let index = 0;
203
+ for (let item; item = this.next(), !item.done; index = (index + 1) % count) {
204
+ ringbuffer[index] = item.value;
152
205
  }
153
- return result;
206
+ return ExtraIterator.from(function* () {
207
+ for (let i = index; i < count + index; i++) {
208
+ yield ringbuffer[i % count];
209
+ }
210
+ }());
154
211
  }
155
212
  drop(count) {
156
213
  return count >= 0
@@ -174,7 +231,11 @@ export class ExtraIterator extends Iterator {
174
231
  * @example ExtraIterator.from([[1, 2], [3, 4]]).flatten().toArray() // returns [1, 2, 3, 4]
175
232
  */
176
233
  flatten() {
177
- return this.flatMap(value => Array.isArray(value) ? new ExtraIterator(value).flatten() : [value]);
234
+ return this.flatMap(value => typeof value === 'object'
235
+ && value !== null
236
+ && Symbol.iterator in value
237
+ ? new ExtraIterator(value).flatten()
238
+ : [value]);
178
239
  }
179
240
  /**
180
241
  * Creates a new iterator that yields the values of this iterator, but won't yield any duplicates.
@@ -271,11 +332,11 @@ export class ExtraIterator extends Iterator {
271
332
  * @example
272
333
  *
273
334
  * ExtraIterator.from([4, 5, 6])
274
- * .prependMany([1, 2, 3])
335
+ * .prependAll([1, 2, 3])
275
336
  * .toArray()
276
337
  * // returns [1, 2, 3, 4, 5, 6]
277
338
  */
278
- prependMany(items) {
339
+ prependAll(items) {
279
340
  return ExtraIterator.from(function* () {
280
341
  yield* items;
281
342
  yield* this;
@@ -351,9 +412,9 @@ export class ExtraIterator extends Iterator {
351
412
  /**
352
413
  * Groups the elements in this iterator into groups of variable size.
353
414
  *
354
- * This method calls the provided predicate function for each pair of adjacent elements in this iterator. It should
355
- * return `true` if the elements should belong to the same group, or `false` if they should belong to different
356
- * groups.
415
+ * This method calls the provided predicate function for each pair of adjacent elements in this iterator. The
416
+ * function should return `true` if the elements should belong to the same group, or `false` if they should belong
417
+ * to different groups.
357
418
  *
358
419
  * The resulting iterator yields arrays of elements that belong to the same group.
359
420
  *
@@ -373,7 +434,7 @@ export class ExtraIterator extends Iterator {
373
434
  let chunk = [first.value];
374
435
  for (let left = first, right, index = 0; right = this.next(), !right.done; left = right, index++) {
375
436
  if (predicate(left.value, right.value, index, chunk)) {
376
- chunk.push(left.value);
437
+ chunk.push(right.value);
377
438
  }
378
439
  else {
379
440
  yield chunk;
@@ -489,8 +550,7 @@ export class ExtraIterator extends Iterator {
489
550
  */
490
551
  splice(startIndex, deleteCount, ...newItems) {
491
552
  if (startIndex < 0) {
492
- return ExtraIterator.from(this.toArray()
493
- .toSpliced(startIndex, deleteCount, ...newItems));
553
+ return ExtraIterator.from(this.toArray().toSpliced(startIndex, deleteCount, ...newItems));
494
554
  }
495
555
  return ExtraIterator.from(function* () {
496
556
  for (let index = 0, next; next = this.next(), !next.done; index++) {
@@ -504,7 +564,8 @@ export class ExtraIterator extends Iterator {
504
564
  }.call(this));
505
565
  }
506
566
  /**
507
- * Returns an iterator the provided element if this iterator is empty; otherwise, it returns this iterator.
567
+ * If this iterator is empty, returns an iterator with the provided element as its only element; otherwise, it
568
+ * returns a copy of this iterator.
508
569
  */
509
570
  defaultIfEmpty(provider) {
510
571
  return ExtraIterator.from(function* () {
@@ -528,9 +589,6 @@ export class ExtraIterator extends Iterator {
528
589
  loop(times = Infinity) {
529
590
  return ExtraIterator.from(function* () {
530
591
  const values = this.toArray();
531
- if (values.length === 0) {
532
- return;
533
- }
534
592
  for (let i = 0; i < times; i++) {
535
593
  yield* values;
536
594
  }
@@ -663,8 +721,8 @@ export class ExtraIterator extends Iterator {
663
721
  *
664
722
  * @example
665
723
  *
666
- * ExtraIterator.from([1, 2, 3]).uniqueness() // returns true
667
- * ExtraIterator.from([1, 2, 3, 1]).uniqueness() // returns false
724
+ * ExtraIterator.from([1, 2, 3]).testUnique() // returns true
725
+ * ExtraIterator.from([1, 2, 3, 1]).testUnique() // returns false
668
726
  */
669
727
  testUnique(mapper) {
670
728
  const seen = new Set();
@@ -7,12 +7,24 @@ describe(ExtraIterator.name, () => {
7
7
  const iterator = ExtraIterator.from(array);
8
8
  expect(iterator.toArray()).toEqual(array);
9
9
  });
10
+ it('should create an ExtraIterator from an "array-like" object', () => {
11
+ const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
12
+ const iterator = ExtraIterator.from(arrayLike);
13
+ expect(iterator.toArray()).toEqual(['a', 'b', 'c']);
14
+ });
10
15
  it('should zip two iterators', () => {
11
16
  const a = ExtraIterator.from([1, 2, 3]);
12
17
  const b = ExtraIterator.from(['a', 'b', 'c']);
13
- const zipped = ExtraIterator.zip(a, b);
18
+ const zipped = a.zip(b);
14
19
  expect(zipped.toArray()).toEqual([[1, 'a'], [2, 'b'], [3, 'c']]);
15
20
  });
21
+ it('should zip three iterators', () => {
22
+ const a = ExtraIterator.from([1, 2, 3]);
23
+ const b = ExtraIterator.from(['a', 'b', 'c']);
24
+ const c = ExtraIterator.from([true, false, true]);
25
+ const zipped = ExtraIterator.zip(a, b, c);
26
+ expect(zipped.toArray()).toEqual([[1, 'a', true], [2, 'b', false], [3, 'c', true]]);
27
+ });
16
28
  it('should count from 0 to a given number', () => {
17
29
  const iterator = ExtraIterator.count().take(5);
18
30
  expect(iterator.toArray()).toEqual([0, 1, 2, 3, 4]);
@@ -25,10 +37,15 @@ describe(ExtraIterator.name, () => {
25
37
  const iterator = ExtraIterator.repeat('x').take(3);
26
38
  expect(iterator.toArray()).toEqual(['x', 'x', 'x']);
27
39
  });
28
- it('should yield random values', () => {
40
+ it('should yield random numbers', () => {
29
41
  const values = ExtraIterator.random().take(10000).toArray();
30
42
  expect(values.length).toBe(10000);
31
- expect(values.every(value => typeof value === 'number' && value >= 0 && value < 256)).toBe(true);
43
+ expect(values.every(value => typeof value === 'number' && value >= 0 && value < 1)).toBe(true);
44
+ });
45
+ it('should yield random bytes', () => {
46
+ const values = ExtraIterator.randomBytes({ chunkSize: 1024 }).take(1).map(chunk => new Uint8Array(chunk)).flatten().toArray();
47
+ expect(values.length).toBe(1024);
48
+ expect(values.every(value => typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 255)).toBe(true);
32
49
  });
33
50
  it('should filter values based on a predicate', () => {
34
51
  const iterator = ExtraIterator.from([1, 2, 3, 4]).filter(x => x % 2 === 0);
@@ -42,6 +59,10 @@ describe(ExtraIterator.name, () => {
42
59
  const iterator = ExtraIterator.from([1, 2, 3, 4]).take(2);
43
60
  expect(iterator.toArray()).toEqual([1, 2]);
44
61
  });
62
+ it('should take the last values of the sequence', () => {
63
+ const iterator = ExtraIterator.from([1, 2, 3, 4]).take(-2);
64
+ expect(iterator.toArray()).toEqual([3, 4]);
65
+ });
45
66
  it('should drop a given number of values', () => {
46
67
  const iterator = ExtraIterator.from([1, 2, 3, 4]).drop(2);
47
68
  expect(iterator.toArray()).toEqual([3, 4]);
@@ -54,9 +75,19 @@ describe(ExtraIterator.name, () => {
54
75
  const iterator = ExtraIterator.from([1, 2, 3]);
55
76
  expect(iterator.first()).toBe(1);
56
77
  });
57
- it('should return the last value', () => {
58
- const iterator = ExtraIterator.from([1, 2, 3]);
59
- expect(iterator.last()).toBe(3);
78
+ it('should have a single value', () => {
79
+ const iterator = ExtraIterator.single(42);
80
+ expect(iterator.first()).toBe(42);
81
+ });
82
+ describe(ExtraIterator.prototype.last.name, () => {
83
+ it('should return the last value if the iterator is not empty', () => {
84
+ const iterator = ExtraIterator.from([1, 2, 3]);
85
+ expect(iterator.last()).toBe(3);
86
+ });
87
+ it('should return undefined if the iterator is empty', () => {
88
+ const iterator = ExtraIterator.empty();
89
+ expect(iterator.last()).toBeUndefined();
90
+ });
60
91
  });
61
92
  it('should return a value at a specific index', () => {
62
93
  const iterator = ExtraIterator.from([1, 2, 3]);
@@ -92,6 +123,10 @@ describe(ExtraIterator.name, () => {
92
123
  const iterator = ExtraIterator.from([2, 3]).prepend(1);
93
124
  expect(iterator.toArray()).toEqual([1, 2, 3]);
94
125
  });
126
+ it('should prepend multiple values to the iterator', () => {
127
+ const iterator = ExtraIterator.from([4, 5, 6]).prependAll([1, 2, 3]);
128
+ expect(iterator.toArray()).toEqual([1, 2, 3, 4, 5, 6]);
129
+ });
95
130
  it('should append a value to the iterator', () => {
96
131
  const iterator = ExtraIterator.from([1, 2]).append(3);
97
132
  expect(iterator.toArray()).toEqual([1, 2, 3]);
@@ -114,9 +149,19 @@ describe(ExtraIterator.name, () => {
114
149
  const iterator = ExtraIterator.from([1, 2, 3, 4]).splice(1, 2, 5, 6);
115
150
  expect(iterator.toArray()).toEqual([1, 5, 6, 4]);
116
151
  });
117
- it('should provide a default value if the iterator is empty', () => {
118
- const iterator = ExtraIterator.empty().defaultIfEmpty(() => 42);
119
- expect(iterator.toArray()).toEqual([42]);
152
+ it('should splice values from the end of the iterator', () => {
153
+ const iterator = ExtraIterator.from([1, 2, 3, 4]).splice(-3, 2, 5, 6);
154
+ expect(iterator.toArray()).toEqual([1, 5, 6, 4]);
155
+ });
156
+ describe('defaultIfEmpty', () => {
157
+ it('should provide a default value if the iterator is empty', () => {
158
+ const iterator = ExtraIterator.empty().defaultIfEmpty(() => 42);
159
+ expect(iterator.toArray()).toEqual([42]);
160
+ });
161
+ it('should relay the iterator itself if it is not empty', () => {
162
+ const iterator = ExtraIterator.from([1, 2, 3]).defaultIfEmpty(() => 42);
163
+ expect(iterator.toArray()).toEqual([1, 2, 3]);
164
+ });
120
165
  });
121
166
  it('should group values by a key', () => {
122
167
  const iterator = ExtraIterator.from(['apple', 'banana', 'apricot']);
@@ -171,6 +216,16 @@ describe(ExtraIterator.name, () => {
171
216
  .toArray();
172
217
  expect(result).toEqual([[1, 1], [2], [3, 3, 3], [2, 2]]);
173
218
  });
219
+ it('should chunk using a key selector function', () => {
220
+ const result = ExtraIterator.from(['apple', 'apricot', 'banana', 'avocado'])
221
+ .chunkWith((lhs, rhs) => lhs[0] === rhs[0])
222
+ .toArray();
223
+ expect(result).toEqual([['apple', 'apricot'], ['banana'], ['avocado']]);
224
+ });
225
+ it('should build an empty iterator for an empty iterator', () => {
226
+ const result = ExtraIterator.from([]).chunkWith((lhs, rhs) => lhs === rhs).toArray();
227
+ expect(result).toEqual([]);
228
+ });
174
229
  });
175
230
  it('should create a chain of responsibility function', () => {
176
231
  const humanizeDuration = ExtraIterator.from([
@@ -191,9 +246,18 @@ describe(ExtraIterator.name, () => {
191
246
  const chain = handlers.toChainOfResponsibilityFunction(next => next());
192
247
  expect(() => chain()).toThrow();
193
248
  });
194
- it('should repeat values', () => {
195
- const iterator = ExtraIterator.from([1, 2, 3]).loop(3);
196
- expect(iterator.toArray()).toEqual([1, 2, 3, 1, 2, 3, 1, 2, 3]);
249
+ describe(ExtraIterator.prototype.loop.name, () => {
250
+ it('should repeat values', () => {
251
+ const iterator = ExtraIterator.from([1, 2, 3]).loop(3);
252
+ expect(iterator.toArray()).toEqual([1, 2, 3, 1, 2, 3, 1, 2, 3]);
253
+ });
254
+ it('should yield an empty iterator if the count is equal to or lower than 0', () => {
255
+ expect(ExtraIterator.from([1, 2, 3]).loop(0).toArray()).toEqual([]);
256
+ expect(ExtraIterator.from([1, 2, 3]).loop(-1).toArray()).toEqual([]);
257
+ expect(ExtraIterator.from([1, 2, 3]).loop(-3).toArray()).toEqual([]);
258
+ expect(ExtraIterator.from([1, 2, 3]).loop(-4).toArray()).toEqual([]);
259
+ expect(ExtraIterator.from([1, 2, 3]).loop(-99999).toArray()).toEqual([]);
260
+ });
197
261
  });
198
262
  describe(ExtraIterator.range.name, () => {
199
263
  it('should iterate over ranges', () => {
@@ -216,4 +280,42 @@ describe(ExtraIterator.name, () => {
216
280
  expect(() => ExtraIterator.range(0, 10, { step: 0 }).toArray()).toThrow();
217
281
  });
218
282
  });
283
+ it('should count the number of elements in the iterator', () => {
284
+ expect(ExtraIterator.from([1, 2, 3]).count()).toBe(3);
285
+ expect(ExtraIterator.empty().count()).toBe(0);
286
+ });
287
+ describe(ExtraIterator.prototype.testUnique.name, () => {
288
+ it('should return true if all values are unique', () => {
289
+ expect(ExtraIterator.from([1, 2, 3]).testUnique()).toBe(true);
290
+ });
291
+ it('should return false if there are duplicate values', () => {
292
+ expect(ExtraIterator.from([1, 2, 2, 3]).testUnique()).toBe(false);
293
+ });
294
+ it('should return true for an empty iterator', () => {
295
+ expect(ExtraIterator.empty().testUnique()).toBe(true);
296
+ });
297
+ });
298
+ describe(ExtraIterator.prototype.withEach.name, () => {
299
+ it('should perform a side effect for each value', () => {
300
+ const sideEffects = [];
301
+ const iterator = ExtraIterator.from([1, 2, 3]).withEach(x => void sideEffects.push(x));
302
+ expect(iterator.toArray()).toEqual([1, 2, 3]);
303
+ expect(sideEffects).toEqual([1, 2, 3]);
304
+ });
305
+ it('should not perform side effects if the iterator is not consumed', () => {
306
+ const sideEffects = [];
307
+ ExtraIterator.from([1, 2, 3]).withEach(x => void sideEffects.push(x));
308
+ expect(sideEffects).toEqual([]);
309
+ });
310
+ it('should perform side effects even if the iterator is only partially consumed', () => {
311
+ const sideEffects = [];
312
+ const iterator = ExtraIterator.from([1, 2, 3]).withEach(x => void sideEffects.push(x));
313
+ iterator.next();
314
+ expect(sideEffects).toEqual([1]);
315
+ });
316
+ it('should perform side effects on objects', () => {
317
+ const iterator = ExtraIterator.from([{ value: 1 }, { value: 2 }, { value: 3 }]).withEach(obj => obj.value *= 2);
318
+ expect(iterator.toArray()).toEqual([{ value: 2 }, { value: 4 }, { value: 6 }]);
319
+ });
320
+ });
219
321
  });
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "An extension of the Iterator class with additional utility helper functions.",
4
4
  "author": "Leonardo Raele <leonardoraele@gmail.com>",
5
5
  "license": "MIT",
6
- "version": "0.11.0",
6
+ "version": "0.13.0",
7
7
  "type": "module",
8
8
  "exports": {
9
9
  ".": "./dist/index.js",
@@ -13,9 +13,11 @@
13
13
  "dist"
14
14
  ],
15
15
  "scripts": {
16
- "test": "run-s test:types test:unit",
16
+ "test": "run-p test:*",
17
17
  "test:types": "tsc --noEmit",
18
18
  "test:unit": "node --import=tsx --test **/*.test.ts",
19
+ "coverage": "run-s build coverage:test",
20
+ "coverage:test": "node --import=tsx --test --experimental-test-coverage",
19
21
  "build": "tsc",
20
22
  "prebuild": "rimraf dist",
21
23
  "prepack": "run-s test build"